( 点击图标即可加入 )Encapsulates the result of decoding a barcode within an image.
* * @author Sean Owen */ public final class Result { private final String text; private final byte[] rawBytes; private final int numBits; private ResultPoint[] resultPoints; private final BarcodeFormat format; private Map2D barcode formats typically encode text, but allow for a sort of 'byte mode' * which is sometimes used to encode binary data. While {@link Result} makes available * the complete raw bytes in the barcode for these formats, it does not offer the bytes * from the byte segments alone.
* *This maps to a {@link java.util.List} of byte arrays corresponding to the * raw bytes in the byte segments in the barcode, in order.
*/ BYTE_SEGMENTS, /** * Error correction level used, if applicable. The value type depends on the * format, but is typically a String. */ ERROR_CORRECTION_LEVEL, /** * For some periodicals, indicates the issue number as an {@link Integer}. */ ISSUE_NUMBER, /** * For some products, indicates the suggested retail price in the barcode as a * formatted {@link String}. */ SUGGESTED_PRICE, /** * For some products, the possible country of manufacture as a {@link String} denoting the * ISO country code. Some map to multiple possible countries, like "US/CA". */ POSSIBLE_COUNTRY, /** * For some products, the extension text */ UPC_EAN_EXTENSION, /** * PDF417-specific metadata */ PDF417_EXTRA_METADATA, /** * If the code format supports structured append and the current scanned code is part of one then the * sequence number is given with it. */ STRUCTURED_APPEND_SEQUENCE, /** * If the code format supports structured append and the current scanned code is part of one then the * parity is given with it. */ STRUCTURED_APPEND_PARITY, } ================================================ FILE: scanner/src/main/java/com/google/zxing/ResultPoint.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing; import com.google.zxing.common.detector.MathUtils; /** *Encapsulates a point of interest in an image containing a barcode. Typically, this * would be the location of a finder pattern or the corner of the barcode, for example.
* * @author Sean Owen */ public class ResultPoint { private final float x; private final float y; public ResultPoint(float x, float y) { this.x = x; this.y = y; } public final float getX() { return x; } public final float getY() { return y; } @Override public final boolean equals(Object other) { if (other instanceof ResultPoint) { ResultPoint otherPoint = (ResultPoint) other; return x == otherPoint.x && y == otherPoint.y; } return false; } @Override public final int hashCode() { return 31 * Float.floatToIntBits(x) + Float.floatToIntBits(y); } @Override public final String toString() { return "(" + x + ',' + y + ')'; } /** * Orders an array of three ResultPoints in an order [A,B,C] such that AB is less than AC * and BC is less than AC, and the angle between BC and BA is less than 180 degrees. * * @param patterns array of three {@code ResultPoint} to order */ public static void orderBestPatterns(ResultPoint[] patterns) { // Find distances between pattern centers float zeroOneDistance = distance(patterns[0], patterns[1]); float oneTwoDistance = distance(patterns[1], patterns[2]); float zeroTwoDistance = distance(patterns[0], patterns[2]); ResultPoint pointA; ResultPoint pointB; ResultPoint pointC; // Assume one closest to other two is B; A and C will just be guesses at first if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) { pointB = patterns[0]; pointA = patterns[1]; pointC = patterns[2]; } else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) { pointB = patterns[1]; pointA = patterns[0]; pointC = patterns[2]; } else { pointB = patterns[2]; pointA = patterns[0]; pointC = patterns[1]; } // Use cross product to figure out whether A and C are correct or flipped. // This asks whether BC x BA has a positive z component, which is the arrangement // we want for A, B, C. If it's negative, then we've got it flipped around and // should swap A and C. if (crossProductZ(pointA, pointB, pointC) < 0.0f) { ResultPoint temp = pointA; pointA = pointC; pointC = temp; } patterns[0] = pointA; patterns[1] = pointB; patterns[2] = pointC; } /** * @param pattern1 first pattern * @param pattern2 second pattern * @return distance between two points */ public static float distance(ResultPoint pattern1, ResultPoint pattern2) { return MathUtils.distance(pattern1.x, pattern1.y, pattern2.x, pattern2.y); } /** * Returns the z component of the cross product between vectors BC and BA. */ private static float crossProductZ(ResultPoint pointA, ResultPoint pointB, ResultPoint pointC) { float bX = pointB.x; float bY = pointB.y; return ((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX)); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/ResultPointCallback.java ================================================ /* * Copyright 2009 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; /** * Callback which is invoked when a possible result point (significant * point in the barcode image such as a corner) is found. * * @see DecodeHintType#NEED_RESULT_POINT_CALLBACK */ public interface ResultPointCallback { void foundPossibleResultPoint(ResultPoint point); } ================================================ FILE: scanner/src/main/java/com/google/zxing/Writer.java ================================================ /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing; import com.google.zxing.common.BitMatrix; import java.util.Map; /** * The base class for all objects which encode/generate a barcode image. * * @author dswitkin@google.com (Daniel Switkin) */ public interface Writer { /** * Encode a barcode using the default settings. * * @param contents The contents to encode in the barcode * @param format The barcode format to generate * @param width The preferred width in pixels * @param height The preferred height in pixels * @return {@link BitMatrix} representing encoded barcode image * @throws WriterException if contents cannot be encoded legally in a format */ BitMatrix encode(String contents, BarcodeFormat format, int width, int height) throws WriterException; /** * @param contents The contents to encode in the barcode * @param format The barcode format to generate * @param width The preferred width in pixels * @param height The preferred height in pixels * @param hints Additional parameters to supply to the encoder * @return {@link BitMatrix} representing encoded barcode image * @throws WriterException if contents cannot be encoded legally in a format */ BitMatrix encode(String contents, BarcodeFormat format, int width, int height, MapExtends {@link DetectorResult} with more information specific to the Aztec format, * like the number of layers and whether it's compact.
* * @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; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/aztec/AztecReader.java ================================================ /* * 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, MapThe main class which implements Aztec Code decoding -- as opposed to locating and extracting * the Aztec Code from an image.
* * @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 = { "", "\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 AztecDetectorResult ddata; public DecoderResult decode(AztecDetectorResult detectorResult) throws FormatException { ddata = detectorResult; BitMatrix matrix = detectorResult.getBits(); boolean[] rawbits = extractBits(matrix); boolean[] correctedBits = correctBits(rawbits); byte[] rawBytes = convertBoolArrayToByteArray(correctedBits); String result = getEncodedData(correctedBits); DecoderResult decoderResult = new DecoderResult(rawBytes, result, null, null); decoderResult.setNumBits(correctedBits.length); return decoderResult; } // This method is used for testing the high-level encoder public static String highLevelDecode(boolean[] correctedBits) { return getEncodedData(correctedBits); } /** * Gets the string encoded in the aztec code bits * * @return the decoded string */ private static String getEncodedData(boolean[] correctedBits) { int endIndex = correctedBits.length; Table latchTable = Table.UPPER; // table most recently latched to Table shiftTable = Table.UPPER; // table to use for the next read StringBuilder result = new StringBuilder(20); 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); result.append((char) 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 (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 { result.append(str); // Go back to whatever mode we had been in shiftTable = latchTable; } } } 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"); } } /** *Performs RS error correction on an array of bits.
* * @return the corrected array * @throws FormatException if the input contains too many errors */ private boolean[] 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 correctedBits; } /** * 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 isSee * * DoCoMo's documentation about the result types represented by subclasses of this class.
* *Thanks to Jeff Griffin for proposing rewrite of these classes that relies less * on exception-based mechanisms during parsing.
* * @author Sean Owen */ abstract class AbstractDoCoMoResultParser extends ResultParser { static String[] matchDoCoMoPrefixedField(String prefix, String rawText, boolean trim) { return matchPrefixedField(prefix, rawText, ';', trim); } static String matchSingleDoCoMoPrefixedField(String prefix, String rawText, boolean trim) { return matchSinglePrefixedField(prefix, rawText, ';', trim); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/AddressBookAUResultParser.java ================================================ /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.client.result; import com.google.zxing.Result; import java.util.ArrayList; import java.util.List; /** * Implements KDDI AU's address book format. See * * http://www.au.kddi.com/ezfactory/tec/two_dimensions/index.html. * (Thanks to Yuzo for translating!) * * @author Sean Owen */ public final class AddressBookAUResultParser extends ResultParser { @Override public AddressBookParsedResult parse(Result result) { String rawText = getMassagedText(result); // MEMORY is mandatory; seems like a decent indicator, as does end-of-record separator CR/LF if (!rawText.contains("MEMORY") || !rawText.contains("\r\n")) { return null; } // NAME1 and NAME2 have specific uses, namely written name and pronunciation, respectively. // Therefore we treat them specially instead of as an array of names. String name = matchSinglePrefixedField("NAME1:", rawText, '\r', true); String pronunciation = matchSinglePrefixedField("NAME2:", rawText, '\r', true); String[] phoneNumbers = matchMultipleValuePrefix("TEL", 3, rawText, true); String[] emails = matchMultipleValuePrefix("MAIL", 3, rawText, true); String note = matchSinglePrefixedField("MEMORY:", rawText, '\r', false); String address = matchSinglePrefixedField("ADD:", rawText, '\r', true); String[] addresses = address == null ? null : new String[] {address}; return new AddressBookParsedResult(maybeWrap(name), null, pronunciation, phoneNumbers, null, emails, null, null, note, addresses, null, null, null, null, null, null); } private static String[] matchMultipleValuePrefix(String prefix, int max, String rawText, boolean trim) { ListAbstract class representing the result of decoding a barcode, as more than * a String -- as some type of structured data. This might be a subclass which represents * a URL, or an e-mail address. {@link ResultParser#parseResult(com.google.zxing.Result)} will turn a raw * decoded string into the most appropriate type of structured representation.
* *Thanks to Jeff Griffin for proposing rewrite of these classes that relies less * on exception-based mechanisms during parsing.
* * @author Sean Owen */ public abstract class ParsedResult { private final ParsedResultType type; protected ParsedResult(ParsedResultType type) { this.type = type; } public final ParsedResultType getType() { return type; } public abstract String getDisplayResult(); @Override public final String toString() { return getDisplayResult(); } public static void maybeAppend(String value, StringBuilder result) { if (value != null && !value.isEmpty()) { // Don't add a newline before the first value if (result.length() > 0) { result.append('\n'); } result.append(value); } } public static void maybeAppend(String[] values, StringBuilder result) { if (values != null) { for (String value : values) { maybeAppend(value, result); } } } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/ParsedResultType.java ================================================ /* * 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.client.result; /** * Represents the type of data encoded by a barcode -- from plain text, to a * URI, to an e-mail address, etc. * * @author Sean Owen */ public enum ParsedResultType { ADDRESSBOOK, EMAIL_ADDRESS, PRODUCT, URI, TEXT, GEO, TEL, SMS, CALENDAR, WIFI, ISBN, VIN, } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/ProductParsedResult.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.client.result; /** * Represents a parsed result that encodes a product by an identifier of some kind. * * @author dswitkin@google.com (Daniel Switkin) */ public final class ProductParsedResult extends ParsedResult { private final String productID; private final String normalizedProductID; ProductParsedResult(String productID) { this(productID, productID); } ProductParsedResult(String productID, String normalizedProductID) { super(ParsedResultType.PRODUCT); this.productID = productID; this.normalizedProductID = normalizedProductID; } public String getProductID() { return productID; } public String getNormalizedProductID() { return normalizedProductID; } @Override public String getDisplayResult() { return productID; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/ProductResultParser.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.client.result; import com.google.zxing.BarcodeFormat; import com.google.zxing.Result; import com.google.zxing.oned.UPCEReader; /** * Parses strings of digits that represent a UPC code. * * @author dswitkin@google.com (Daniel Switkin) */ public final class ProductResultParser extends ResultParser { // Treat all UPC and EAN variants as UPCs, in the sense that they are all product barcodes. @Override public ProductParsedResult parse(Result result) { BarcodeFormat format = result.getBarcodeFormat(); if (!(format == BarcodeFormat.UPC_A || format == BarcodeFormat.UPC_E || format == BarcodeFormat.EAN_8 || format == BarcodeFormat.EAN_13)) { return null; } String rawText = getMassagedText(result); if (!isStringOfDigits(rawText, rawText.length())) { return null; } // Not actually checking the checksum again here String normalizedProductID; // Expand UPC-E for purposes of searching if (format == BarcodeFormat.UPC_E && rawText.length() == 8) { normalizedProductID = UPCEReader.convertUPCEtoUPCA(rawText); } else { normalizedProductID = rawText; } return new ProductParsedResult(rawText, normalizedProductID); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/ResultParser.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.client.result; import com.google.zxing.Result; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; /** *Abstract class representing the result of decoding a barcode, as more than * a String -- as some type of structured data. This might be a subclass which represents * a URL, or an e-mail address. {@link #parseResult(Result)} will turn a raw * decoded string into the most appropriate type of structured representation.
* *Thanks to Jeff Griffin for proposing rewrite of these classes that relies less * on exception-based mechanisms during parsing.
* * @author Sean Owen */ public abstract class ResultParser { private static final ResultParser[] PARSERS = { new BookmarkDoCoMoResultParser(), new AddressBookDoCoMoResultParser(), new EmailDoCoMoResultParser(), new AddressBookAUResultParser(), new VCardResultParser(), new BizcardResultParser(), new VEventResultParser(), new EmailAddressResultParser(), new SMTPResultParser(), new TelResultParser(), new SMSMMSResultParser(), new SMSTOMMSTOResultParser(), new GeoResultParser(), new WifiResultParser(), new URLTOResultParser(), new URIResultParser(), new ISBNResultParser(), new ProductResultParser(), new ExpandedProductResultParser(), new VINResultParser(), }; private static final Pattern DIGITS = Pattern.compile("\\d+"); private static final Pattern AMPERSAND = Pattern.compile("&"); private static final Pattern EQUALS = Pattern.compile("="); private static final String BYTE_ORDER_MARK = "\ufeff"; static final String[] EMPTY_STR_ARRAY = new String[0]; /** * Attempts to parse the raw {@link Result}'s contents as a particular type * of information (email, URL, etc.) and return a {@link ParsedResult} encapsulating * the result of parsing. * * @param theResult the raw {@link Result} to parse * @return {@link ParsedResult} encapsulating the parsing result */ public abstract ParsedResult parse(Result theResult); protected static String getMassagedText(Result result) { String text = result.getText(); if (text.startsWith(BYTE_ORDER_MARK)) { text = text.substring(1); } return text; } public static ParsedResult parseResult(Result theResult) { for (ResultParser parser : PARSERS) { ParsedResult result = parser.parse(theResult); if (result != null) { return result; } } return new TextParsedResult(theResult.getText(), null); } protected static void maybeAppend(String value, StringBuilder result) { if (value != null) { result.append('\n'); result.append(value); } } protected static void maybeAppend(String[] value, StringBuilder result) { if (value != null) { for (String s : value) { result.append('\n'); result.append(s); } } } protected static String[] maybeWrap(String value) { return value == null ? null : new String[] { value }; } protected static String unescapeBackslash(String escaped) { int backslash = escaped.indexOf('\\'); if (backslash < 0) { return escaped; } int max = escaped.length(); StringBuilder unescaped = new StringBuilder(max - 1); unescaped.append(escaped.toCharArray(), 0, backslash); boolean nextIsEscaped = false; for (int i = backslash; i < max; i++) { char c = escaped.charAt(i); if (nextIsEscaped || c != '\\') { unescaped.append(c); nextIsEscaped = false; } else { nextIsEscaped = true; } } return unescaped.toString(); } protected static int parseHexDigit(char c) { if (c >= '0' && c <= '9') { return c - '0'; } if (c >= 'a' && c <= 'f') { return 10 + (c - 'a'); } if (c >= 'A' && c <= 'F') { return 10 + (c - 'A'); } return -1; } protected static boolean isStringOfDigits(CharSequence value, int length) { return value != null && length > 0 && length == value.length() && DIGITS.matcher(value).matches(); } protected static boolean isSubstringOfDigits(CharSequence value, int offset, int length) { if (value == null || length <= 0) { return false; } int max = offset + length; return value.length() >= max && DIGITS.matcher(value.subSequence(offset, max)).matches(); } static MapParses an "sms:" URI result, which specifies a number to SMS. * See RFC 5724 on this.
* *This class supports "via" syntax for numbers, which is not part of the spec. * For example "+12125551212;via=+12124440101" may appear as a number. * It also supports a "subject" query parameter, which is not mentioned in the spec. * These are included since they were mentioned in earlier IETF drafts and might be * used.
* *This actually also parses URIs starting with "mms:" and treats them all the same way, * and effectively converts them to an "sms:" URI for purposes of forwarding to the platform.
* * @author Sean Owen */ public final class SMSMMSResultParser extends ResultParser { @Override public SMSParsedResult parse(Result result) { String rawText = getMassagedText(result); if (!(rawText.startsWith("sms:") || rawText.startsWith("SMS:") || rawText.startsWith("mms:") || rawText.startsWith("MMS:"))) { return null; } // Check up front if this is a URI syntax string with query arguments MapParses an "smsto:" URI result, whose format is not standardized but appears to be like: * {@code smsto:number(:body)}.
* *This actually also parses URIs starting with "smsto:", "mmsto:", "SMSTO:", and * "MMSTO:", and treats them all the same way, and effectively converts them to an "sms:" URI * for purposes of forwarding to the platform.
* * @author Sean Owen */ public final class SMSTOMMSTOResultParser extends ResultParser { @Override public SMSParsedResult parse(Result result) { String rawText = getMassagedText(result); if (!(rawText.startsWith("smsto:") || rawText.startsWith("SMSTO:") || rawText.startsWith("mmsto:") || rawText.startsWith("MMSTO:"))) { return null; } // Thanks to dominik.wild for suggesting this enhancement to support // smsto:number:body URIs String number = rawText.substring(6); String body = null; int bodyStart = number.indexOf(':'); if (bodyStart >= 0) { body = number.substring(bodyStart + 1); number = number.substring(0, bodyStart); } return new SMSParsedResult(number, null, null, body); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/SMTPResultParser.java ================================================ /* * 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.client.result; import com.google.zxing.Result; /** *Parses an "smtp:" URI result, whose format is not standardized but appears to be like: * {@code smtp[:subject[:body]]}.
* * @author Sean Owen */ public final class SMTPResultParser extends ResultParser { @Override public EmailAddressParsedResult parse(Result result) { String rawText = getMassagedText(result); if (!(rawText.startsWith("smtp:") || rawText.startsWith("SMTP:"))) { return null; } String emailAddress = rawText.substring(5); String subject = null; String body = null; int colon = emailAddress.indexOf(':'); if (colon >= 0) { subject = emailAddress.substring(colon + 1); emailAddress = emailAddress.substring(0, colon); colon = subject.indexOf(':'); if (colon >= 0) { body = subject.substring(colon + 1); subject = subject.substring(0, colon); } } return new EmailAddressParsedResult(new String[] {emailAddress}, null, null, subject, body); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/TelParsedResult.java ================================================ /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.client.result; /** * Represents a parsed result that encodes a telephone number. * * @author Sean Owen */ public final class TelParsedResult extends ParsedResult { private final String number; private final String telURI; private final String title; public TelParsedResult(String number, String telURI, String title) { super(ParsedResultType.TEL); this.number = number; this.telURI = telURI; this.title = title; } public String getNumber() { return number; } public String getTelURI() { return telURI; } public String getTitle() { return title; } @Override public String getDisplayResult() { StringBuilder result = new StringBuilder(20); maybeAppend(number, result); maybeAppend(title, result); return result.toString(); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/TelResultParser.java ================================================ /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.client.result; import com.google.zxing.Result; /** * Parses a "tel:" URI result, which specifies a phone number. * * @author Sean Owen */ public final class TelResultParser extends ResultParser { @Override public TelParsedResult parse(Result result) { String rawText = getMassagedText(result); if (!rawText.startsWith("tel:") && !rawText.startsWith("TEL:")) { return null; } // Normalize "TEL:" to "tel:" String telURI = rawText.startsWith("TEL:") ? "tel:" + rawText.substring(4) : rawText; // Drop tel, query portion int queryStart = rawText.indexOf('?', 4); String number = queryStart < 0 ? rawText.substring(4) : rawText.substring(4, queryStart); return new TelParsedResult(number, telURI, null); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/TextParsedResult.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.client.result; /** * A simple result type encapsulating a string that has no further * interpretation. * * @author Sean Owen */ public final class TextParsedResult extends ParsedResult { private final String text; private final String language; public TextParsedResult(String text, String language) { super(ParsedResultType.TEXT); this.text = text; this.language = language; } public String getText() { return text; } public String getLanguage() { return language; } @Override public String getDisplayResult() { return text; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/URIParsedResult.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.client.result; /** * A simple result type encapsulating a URI that has no further interpretation. * * @author Sean Owen */ public final class URIParsedResult extends ParsedResult { private final String uri; private final String title; public URIParsedResult(String uri, String title) { super(ParsedResultType.URI); this.uri = massageURI(uri); this.title = title; } public String getURI() { return uri; } public String getTitle() { return title; } /** * @return true if the URI contains suspicious patterns that may suggest it intends to * mislead the user about its true nature * @deprecated see {@link URIResultParser#isPossiblyMaliciousURI(String)} */ @Deprecated public boolean isPossiblyMaliciousURI() { return URIResultParser.isPossiblyMaliciousURI(uri); } @Override public String getDisplayResult() { StringBuilder result = new StringBuilder(30); maybeAppend(title, result); maybeAppend(uri, result); return result.toString(); } /** * Transforms a string that represents a URI into something more proper, by adding or canonicalizing * the protocol. */ private static String massageURI(String uri) { uri = uri.trim(); int protocolEnd = uri.indexOf(':'); if (protocolEnd < 0 || isColonFollowedByPortNumber(uri, protocolEnd)) { // No protocol, or found a colon, but it looks like it is after the host, so the protocol is still missing, // so assume http uri = "http://" + uri; } return uri; } private static boolean isColonFollowedByPortNumber(String uri, int protocolEnd) { int start = protocolEnd + 1; int nextSlash = uri.indexOf('/', start); if (nextSlash < 0) { nextSlash = uri.length(); } return ResultParser.isSubstringOfDigits(uri, start, nextSlash - start); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/URIResultParser.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.client.result; import com.google.zxing.Result; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Tries to parse results that are a URI of some kind. * * @author Sean Owen */ public final class URIResultParser extends ResultParser { private static final Pattern ALLOWED_URI_CHARS_PATTERN = Pattern.compile("[-._~:/?#\\[\\]@!$&'()*+,;=%A-Za-z0-9]+"); private static final Pattern USER_IN_HOST = Pattern.compile(":/*([^/@]+)@[^/]+"); // See http://www.ietf.org/rfc/rfc2396.txt private static final Pattern URL_WITH_PROTOCOL_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9+-.]+:"); private static final Pattern URL_WITHOUT_PROTOCOL_PATTERN = Pattern.compile( "([a-zA-Z0-9\\-]+\\.){1,6}[a-zA-Z]{2,}" + // host name elements; allow up to say 6 domain elements "(:\\d{1,5})?" + // maybe port "(/|\\?|$)"); // query, path or nothing @Override public URIParsedResult parse(Result result) { String rawText = getMassagedText(result); // We specifically handle the odd "URL" scheme here for simplicity and add "URI" for fun // Assume anything starting this way really means to be a URI if (rawText.startsWith("URL:") || rawText.startsWith("URI:")) { return new URIParsedResult(rawText.substring(4).trim(), null); } rawText = rawText.trim(); if (!isBasicallyValidURI(rawText) || isPossiblyMaliciousURI(rawText)) { return null; } return new URIParsedResult(rawText, null); } /** * @return true if the URI contains suspicious patterns that may suggest it intends to * mislead the user about its true nature. At the moment this looks for the presence * of user/password syntax in the host/authority portion of a URI which may be used * in attempts to make the URI's host appear to be other than it is. Example: * http://yourbank.com@phisher.com This URI connects to phisher.com but may appear * to connect to yourbank.com at first glance. */ static boolean isPossiblyMaliciousURI(String uri) { return !ALLOWED_URI_CHARS_PATTERN.matcher(uri).matches() || USER_IN_HOST.matcher(uri).find(); } static boolean isBasicallyValidURI(String uri) { if (uri.contains(" ")) { // Quick hack check for a common case return false; } Matcher m = URL_WITH_PROTOCOL_PATTERN.matcher(uri); if (m.find() && m.start() == 0) { // match at start only return true; } m = URL_WITHOUT_PROTOCOL_PATTERN.matcher(uri); return m.find() && m.start() == 0; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/URLTOResultParser.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.client.result; import com.google.zxing.Result; /** * Parses the "URLTO" result format, which is of the form "URLTO:[title]:[url]". * This seems to be used sometimes, but I am not able to find documentation * on its origin or official format? * * @author Sean Owen */ public final class URLTOResultParser extends ResultParser { @Override public URIParsedResult parse(Result result) { String rawText = getMassagedText(result); if (!rawText.startsWith("urlto:") && !rawText.startsWith("URLTO:")) { return null; } int titleEnd = rawText.indexOf(':', 6); if (titleEnd < 0) { return null; } String title = titleEnd <= 6 ? null : rawText.substring(6, titleEnd); String uri = rawText.substring(titleEnd + 1); return new URIParsedResult(uri, title); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/client/result/VCardResultParser.java ================================================ /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.client.result; import com.google.zxing.Result; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Parses contact information formatted according to the VCard (2.1) format. This is not a complete * implementation but should parse information as commonly encoded in 2D barcodes. * * @author Sean Owen */ public final class VCardResultParser extends ResultParser { private static final Pattern BEGIN_VCARD = Pattern.compile("BEGIN:VCARD", Pattern.CASE_INSENSITIVE); private static final Pattern VCARD_LIKE_DATE = Pattern.compile("\\d{4}-?\\d{2}-?\\d{2}"); private static final Pattern CR_LF_SPACE_TAB = Pattern.compile("\r\n[ \t]"); private static final Pattern NEWLINE_ESCAPE = Pattern.compile("\\\\[nN]"); private static final Pattern VCARD_ESCAPES = Pattern.compile("\\\\([,;\\\\])"); private static final Pattern EQUALS = Pattern.compile("="); private static final Pattern SEMICOLON = Pattern.compile(";"); private static final Pattern UNESCAPED_SEMICOLONS = Pattern.compile("(?> names = matchVCardPrefixedField("FN", rawText, true, false); if (names == null) { // If no display names found, look for regular name fields and format them names = matchVCardPrefixedField("N", rawText, true, false); formatNames(names); } ListParses a WIFI configuration string. Strings will be of the form:
* *{@code WIFI:T:[network type];S:[network SSID];P:[network password];H:[hidden?];;}
* *For WPA2 enterprise (EAP), strings will be of the form:
* *{@code WIFI:T:WPA2-EAP;S:[network SSID];H:[hidden?];E:[EAP method];H:[Phase 2 method];A:[anonymous identity];I:[username];P:[password];;}
* *"EAP method" can e.g. be "TTLS" or "PWD" or one of the other fields in WifiEnterpriseConfig.Eap and "Phase 2 method" can e.g. be "MSCHAPV2" or any of the other fields in WifiEnterpriseConfig.Phase2
* *The fields can appear in any order. Only "S:" is required.
* * @author Vikram Aggarwal * @author Sean Owen * @author Steffen Kieß */ public final class WifiResultParser extends ResultParser { @Override public WifiParsedResult parse(Result result) { String rawText = getMassagedText(result); if (!rawText.startsWith("WIFI:")) { return null; } rawText = rawText.substring("WIFI:".length()); String ssid = matchSinglePrefixedField("S:", rawText, ';', false); if (ssid == null || ssid.isEmpty()) { return null; } String pass = matchSinglePrefixedField("P:", rawText, ';', false); String type = matchSinglePrefixedField("T:", rawText, ';', false); if (type == null) { type = "nopass"; } boolean hidden = Boolean.parseBoolean(matchSinglePrefixedField("H:", rawText, ';', false)); String identity = matchSinglePrefixedField("I:", rawText, ';', false); String anonymousIdentity = matchSinglePrefixedField("A:", rawText, ';', false); String eapMethod = matchSinglePrefixedField("E:", rawText, ';', false); String phase2Method = matchSinglePrefixedField("H:", rawText, ';', false); return new WifiParsedResult(type, ssid, pass, hidden, identity, anonymousIdentity, eapMethod, phase2Method); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/BitArray.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.common; import java.util.Arrays; /** *A simple, fast array of bits, represented compactly by an array of ints internally.
* * @author Sean Owen */ public final class BitArray implements Cloneable { private int[] bits; private int size; public BitArray() { this.size = 0; this.bits = new int[1]; } public BitArray(int size) { this.size = size; this.bits = makeArray(size); } // For testing only BitArray(int[] bits, int size) { this.bits = bits; this.size = size; } public int getSize() { return size; } public int getSizeInBytes() { return (size + 7) / 8; } private void ensureCapacity(int size) { if (size > bits.length * 32) { int[] newBits = makeArray(size); System.arraycopy(bits, 0, newBits, 0, bits.length); this.bits = newBits; } } /** * @param i bit to get * @return true iff bit i is set */ public boolean get(int i) { return (bits[i / 32] & (1 << (i & 0x1F))) != 0; } /** * Sets bit i. * * @param i bit to set */ public void set(int i) { bits[i / 32] |= 1 << (i & 0x1F); } /** * Flips bit i. * * @param i bit to set */ public void flip(int i) { bits[i / 32] ^= 1 << (i & 0x1F); } /** * @param from first bit to check * @return index of first bit that is set, starting from the given index, or size if none are set * at or beyond this given index * @see #getNextUnset(int) */ public int getNextSet(int from) { if (from >= size) { return size; } int bitsOffset = from / 32; int currentBits = bits[bitsOffset]; // mask off lesser bits first currentBits &= -(1 << (from & 0x1F)); while (currentBits == 0) { if (++bitsOffset == bits.length) { return size; } currentBits = bits[bitsOffset]; } int result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits); return result > size ? size : result; } /** * @param from index to start looking for unset bit * @return index of next unset bit, or {@code size} if none are unset until the end * @see #getNextSet(int) */ public int getNextUnset(int from) { if (from >= size) { return size; } int bitsOffset = from / 32; int currentBits = ~bits[bitsOffset]; // mask off lesser bits first currentBits &= -(1 << (from & 0x1F)); while (currentBits == 0) { if (++bitsOffset == bits.length) { return size; } currentBits = ~bits[bitsOffset]; } int result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits); return result > size ? size : result; } /** * Sets a block of 32 bits, starting at bit i. * * @param i first bit to set * @param newBits the new value of the next 32 bits. Note again that the least-significant bit * corresponds to bit i, the next-least-significant to i+1, and so on. */ public void setBulk(int i, int newBits) { bits[i / 32] = newBits; } /** * Sets a range of bits. * * @param start start of range, inclusive. * @param end end of range, exclusive */ public void setRange(int start, int end) { if (end < start || start < 0 || end > size) { throw new IllegalArgumentException(); } if (end == start) { return; } end--; // will be easier to treat this as the last actually set bit -- inclusive int firstInt = start / 32; int lastInt = end / 32; for (int i = firstInt; i <= lastInt; i++) { int firstBit = i > firstInt ? 0 : start & 0x1F; int lastBit = i < lastInt ? 31 : end & 0x1F; // Ones from firstBit to lastBit, inclusive int mask = (2 << lastBit) - (1 << firstBit); bits[i] |= mask; } } /** * Clears all bits (sets to false). */ public void clear() { int max = bits.length; for (int i = 0; i < max; i++) { bits[i] = 0; } } /** * Efficient method to check if a range of bits is set, or not set. * * @param start start of range, inclusive. * @param end end of range, exclusive * @param value if true, checks that bits in range are set, otherwise checks that they are not set * @return true iff all bits are set or not set in range, according to value argument * @throws IllegalArgumentException if end is less than start or the range is not contained in the array */ public boolean isRange(int start, int end, boolean value) { if (end < start || start < 0 || end > size) { throw new IllegalArgumentException(); } if (end == start) { return true; // empty range matches } end--; // will be easier to treat this as the last actually set bit -- inclusive int firstInt = start / 32; int lastInt = end / 32; for (int i = firstInt; i <= lastInt; i++) { int firstBit = i > firstInt ? 0 : start & 0x1F; int lastBit = i < lastInt ? 31 : end & 0x1F; // Ones from firstBit to lastBit, inclusive int mask = (2 << lastBit) - (1 << firstBit); // Return false if we're looking for 1s and the masked bits[i] isn't all 1s (that is, // equals the mask, or we're looking for 0s and the masked portion is not all 0s if ((bits[i] & mask) != (value ? mask : 0)) { return false; } } return true; } public void appendBit(boolean bit) { ensureCapacity(size + 1); if (bit) { bits[size / 32] |= 1 << (size & 0x1F); } size++; } /** * Appends the least-significant bits, from value, in order from most-significant to * least-significant. For example, appending 6 bits from 0x000001E will append the bits * 0, 1, 1, 1, 1, 0 in that order. * * @param value {@code int} containing bits to append * @param numBits bits from value to append */ public void appendBits(int value, int numBits) { if (numBits < 0 || numBits > 32) { throw new IllegalArgumentException("Num bits must be between 0 and 32"); } ensureCapacity(size + numBits); for (int numBitsLeft = numBits; numBitsLeft > 0; numBitsLeft--) { appendBit(((value >> (numBitsLeft - 1)) & 0x01) == 1); } } public void appendBitArray(BitArray other) { int otherSize = other.size; ensureCapacity(size + otherSize); for (int i = 0; i < otherSize; i++) { appendBit(other.get(i)); } } public void xor(BitArray other) { if (size != other.size) { throw new IllegalArgumentException("Sizes don't match"); } for (int i = 0; i < bits.length; i++) { // The last int could be incomplete (i.e. not have 32 bits in // it) but there is no problem since 0 XOR 0 == 0. bits[i] ^= other.bits[i]; } } /** * * @param bitOffset first bit to start writing * @param array array to write into. Bytes are written most-significant byte first. This is the opposite * of the internal representation, which is exposed by {@link #getBitArray()} * @param offset position in array to start writing * @param numBytes how many bytes to write */ public void toBytes(int bitOffset, byte[] array, int offset, int numBytes) { for (int i = 0; i < numBytes; i++) { int theByte = 0; for (int j = 0; j < 8; j++) { if (get(bitOffset)) { theByte |= 1 << (7 - j); } bitOffset++; } array[offset + i] = (byte) theByte; } } /** * @return underlying array of ints. The first element holds the first 32 bits, and the least * significant bit is bit 0. */ public int[] getBitArray() { return bits; } /** * Reverses all bits in the array. */ public void reverse() { int[] newBits = new int[bits.length]; // reverse all int's first int len = (size - 1) / 32; int oldBitsLen = len + 1; for (int i = 0; i < oldBitsLen; i++) { long x = bits[i]; x = ((x >> 1) & 0x55555555L) | ((x & 0x55555555L) << 1); x = ((x >> 2) & 0x33333333L) | ((x & 0x33333333L) << 2); x = ((x >> 4) & 0x0f0f0f0fL) | ((x & 0x0f0f0f0fL) << 4); x = ((x >> 8) & 0x00ff00ffL) | ((x & 0x00ff00ffL) << 8); x = ((x >> 16) & 0x0000ffffL) | ((x & 0x0000ffffL) << 16); newBits[len - i] = (int) x; } // now correct the int's if the bit size isn't a multiple of 32 if (size != oldBitsLen * 32) { int leftOffset = oldBitsLen * 32 - size; int currentInt = newBits[0] >>> leftOffset; for (int i = 1; i < oldBitsLen; i++) { int nextInt = newBits[i]; currentInt |= nextInt << (32 - leftOffset); newBits[i - 1] = currentInt; currentInt = nextInt >>> leftOffset; } newBits[oldBitsLen - 1] = currentInt; } bits = newBits; } private static int[] makeArray(int size) { return new int[(size + 31) / 32]; } @Override public boolean equals(Object o) { if (!(o instanceof BitArray)) { return false; } BitArray other = (BitArray) o; return size == other.size && Arrays.equals(bits, other.bits); } @Override public int hashCode() { return 31 * size + Arrays.hashCode(bits); } @Override public String toString() { StringBuilder result = new StringBuilder(size + (size / 8) + 1); for (int i = 0; i < size; i++) { if ((i & 0x07) == 0) { result.append(' '); } result.append(get(i) ? 'X' : '.'); } return result.toString(); } @Override public BitArray clone() { return new BitArray(bits.clone(), size); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/BitMatrix.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.common; import java.util.Arrays; /** *Represents a 2D matrix of bits. In function arguments below, and throughout the common * module, x is the column position, and y is the row position. The ordering is always x, y. * The origin is at the top-left.
* *Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins * with a new int. This is done intentionally so that we can copy out a row into a BitArray very * efficiently.
* *The ordering of bits is row-major. Within each int, the least significant bits are used first, * meaning they represent lower x values. This is compatible with BitArray's implementation.
* * @author Sean Owen * @author dswitkin@google.com (Daniel Switkin) */ public final class BitMatrix implements Cloneable { private final int width; private final int height; private final int rowSize; private final int[] bits; /** * Creates an empty square {@code BitMatrix}. * * @param dimension height and width */ public BitMatrix(int dimension) { this(dimension, dimension); } /** * Creates an empty {@code BitMatrix}. * * @param width bit matrix width * @param height bit matrix height */ public BitMatrix(int width, int height) { if (width < 1 || height < 1) { throw new IllegalArgumentException("Both dimensions must be greater than 0"); } this.width = width; this.height = height; this.rowSize = (width + 31) / 32; bits = new int[rowSize * height]; } private BitMatrix(int width, int height, int rowSize, int[] bits) { this.width = width; this.height = height; this.rowSize = rowSize; this.bits = bits; } /** * Interprets a 2D array of booleans as a {@code BitMatrix}, where "true" means an "on" bit. * * @param image bits of the image, as a row-major 2D array. Elements are arrays representing rows * @return {@code BitMatrix} representation of image */ public static BitMatrix parse(boolean[][] image) { int height = image.length; int width = image[0].length; BitMatrix bits = new BitMatrix(width, height); for (int i = 0; i < height; i++) { boolean[] imageI = image[i]; for (int j = 0; j < width; j++) { if (imageI[j]) { bits.set(j, i); } } } return bits; } public static BitMatrix parse(String stringRepresentation, String setString, String unsetString) { if (stringRepresentation == null) { throw new IllegalArgumentException(); } boolean[] bits = new boolean[stringRepresentation.length()]; int bitsPos = 0; int rowStartPos = 0; int rowLength = -1; int nRows = 0; int pos = 0; while (pos < stringRepresentation.length()) { if (stringRepresentation.charAt(pos) == '\n' || stringRepresentation.charAt(pos) == '\r') { if (bitsPos > rowStartPos) { if (rowLength == -1) { rowLength = bitsPos - rowStartPos; } else if (bitsPos - rowStartPos != rowLength) { throw new IllegalArgumentException("row lengths do not match"); } rowStartPos = bitsPos; nRows++; } pos++; } else if (stringRepresentation.substring(pos, pos + setString.length()).equals(setString)) { pos += setString.length(); bits[bitsPos] = true; bitsPos++; } else if (stringRepresentation.substring(pos, pos + unsetString.length()).equals(unsetString)) { pos += unsetString.length(); bits[bitsPos] = false; bitsPos++; } else { throw new IllegalArgumentException( "illegal character encountered: " + stringRepresentation.substring(pos)); } } // no EOL at end? if (bitsPos > rowStartPos) { if (rowLength == -1) { rowLength = bitsPos - rowStartPos; } else if (bitsPos - rowStartPos != rowLength) { throw new IllegalArgumentException("row lengths do not match"); } nRows++; } BitMatrix matrix = new BitMatrix(rowLength, nRows); for (int i = 0; i < bitsPos; i++) { if (bits[i]) { matrix.set(i % rowLength, i / rowLength); } } return matrix; } /** *Gets the requested bit, where true means black.
* * @param x The horizontal component (i.e. which column) * @param y The vertical component (i.e. which row) * @return value of given bit in matrix */ public boolean get(int x, int y) { int offset = y * rowSize + (x / 32); return ((bits[offset] >>> (x & 0x1f)) & 1) != 0; } /** *Sets the given bit to true.
* * @param x The horizontal component (i.e. which column) * @param y The vertical component (i.e. which row) */ public void set(int x, int y) { int offset = y * rowSize + (x / 32); bits[offset] |= 1 << (x & 0x1f); } public void unset(int x, int y) { int offset = y * rowSize + (x / 32); bits[offset] &= ~(1 << (x & 0x1f)); } /** *Flips the given bit.
* * @param x The horizontal component (i.e. which column) * @param y The vertical component (i.e. which row) */ public void flip(int x, int y) { int offset = y * rowSize + (x / 32); bits[offset] ^= 1 << (x & 0x1f); } /** * Exclusive-or (XOR): Flip the bit in this {@code BitMatrix} if the corresponding * mask bit is set. * * @param mask XOR mask */ public void xor(BitMatrix mask) { if (width != mask.getWidth() || height != mask.getHeight() || rowSize != mask.getRowSize()) { throw new IllegalArgumentException("input matrix dimensions do not match"); } BitArray rowArray = new BitArray(width); for (int y = 0; y < height; y++) { int offset = y * rowSize; int[] row = mask.getRow(y, rowArray).getBitArray(); for (int x = 0; x < rowSize; x++) { bits[offset + x] ^= row[x]; } } } /** * Clears all bits (sets to false). */ public void clear() { int max = bits.length; for (int i = 0; i < max; i++) { bits[i] = 0; } } /** *Sets a square region of the bit matrix to true.
* * @param left The horizontal position to begin at (inclusive) * @param top The vertical position to begin at (inclusive) * @param width The width of the region * @param height The height of the region */ public void setRegion(int left, int top, int width, int height) { if (top < 0 || left < 0) { throw new IllegalArgumentException("Left and top must be nonnegative"); } if (height < 1 || width < 1) { throw new IllegalArgumentException("Height and width must be at least 1"); } int right = left + width; int bottom = top + height; if (bottom > this.height || right > this.width) { throw new IllegalArgumentException("The region must fit inside the matrix"); } for (int y = top; y < bottom; y++) { int offset = y * rowSize; for (int x = left; x < right; x++) { bits[offset + (x / 32)] |= 1 << (x & 0x1f); } } } /** * A fast method to retrieve one row of data from the matrix as a BitArray. * * @param y The row to retrieve * @param row An optional caller-allocated BitArray, will be allocated if null or too small * @return The resulting BitArray - this reference should always be used even when passing * your own row */ public BitArray getRow(int y, BitArray row) { if (row == null || row.getSize() < width) { row = new BitArray(width); } else { row.clear(); } int offset = y * rowSize; for (int x = 0; x < rowSize; x++) { row.setBulk(x * 32, bits[offset + x]); } return row; } /** * @param y row to set * @param row {@link BitArray} to copy from */ public void setRow(int y, BitArray row) { System.arraycopy(row.getBitArray(), 0, bits, y * rowSize, rowSize); } /** * Modifies this {@code BitMatrix} to represent the same but rotated 180 degrees */ public void rotate180() { int width = getWidth(); int height = getHeight(); BitArray topRow = new BitArray(width); BitArray bottomRow = new BitArray(width); for (int i = 0; i < (height + 1) / 2; i++) { topRow = getRow(i, topRow); bottomRow = getRow(height - 1 - i, bottomRow); topRow.reverse(); bottomRow.reverse(); setRow(i, bottomRow); setRow(height - 1 - i, topRow); } } /** * This is useful in detecting the enclosing rectangle of a 'pure' barcode. * * @return {@code left,top,width,height} enclosing rectangle of all 1 bits, or null if it is all white */ public int[] getEnclosingRectangle() { int left = width; int top = height; int right = -1; int bottom = -1; for (int y = 0; y < height; y++) { for (int x32 = 0; x32 < rowSize; x32++) { int theBits = bits[y * rowSize + x32]; if (theBits != 0) { if (y < top) { top = y; } if (y > bottom) { bottom = y; } if (x32 * 32 < left) { int bit = 0; while ((theBits << (31 - bit)) == 0) { bit++; } if ((x32 * 32 + bit) < left) { left = x32 * 32 + bit; } } if (x32 * 32 + 31 > right) { int bit = 31; while ((theBits >>> bit) == 0) { bit--; } if ((x32 * 32 + bit) > right) { right = x32 * 32 + bit; } } } } } if (right < left || bottom < top) { return null; } return new int[] {left, top, right - left + 1, bottom - top + 1}; } /** * This is useful in detecting a corner of a 'pure' barcode. * * @return {@code x,y} coordinate of top-left-most 1 bit, or null if it is all white */ public int[] getTopLeftOnBit() { int bitsOffset = 0; while (bitsOffset < bits.length && bits[bitsOffset] == 0) { bitsOffset++; } if (bitsOffset == bits.length) { return null; } int y = bitsOffset / rowSize; int x = (bitsOffset % rowSize) * 32; int theBits = bits[bitsOffset]; int bit = 0; while ((theBits << (31 - bit)) == 0) { bit++; } x += bit; return new int[] {x, y}; } public int[] getBottomRightOnBit() { int bitsOffset = bits.length - 1; while (bitsOffset >= 0 && bits[bitsOffset] == 0) { bitsOffset--; } if (bitsOffset < 0) { return null; } int y = bitsOffset / rowSize; int x = (bitsOffset % rowSize) * 32; int theBits = bits[bitsOffset]; int bit = 31; while ((theBits >>> bit) == 0) { bit--; } x += bit; return new int[] {x, y}; } /** * @return The width of the matrix */ public int getWidth() { return width; } /** * @return The height of the matrix */ public int getHeight() { return height; } /** * @return The row size of the matrix */ public int getRowSize() { return rowSize; } @Override public boolean equals(Object o) { if (!(o instanceof BitMatrix)) { return false; } BitMatrix other = (BitMatrix) o; return width == other.width && height == other.height && rowSize == other.rowSize && Arrays.equals(bits, other.bits); } @Override public int hashCode() { int hash = width; hash = 31 * hash + width; hash = 31 * hash + height; hash = 31 * hash + rowSize; hash = 31 * hash + Arrays.hashCode(bits); return hash; } /** * @return string representation using "X" for set and " " for unset bits */ @Override public String toString() { return toString("X ", " "); } /** * @param setString representation of a set bit * @param unsetString representation of an unset bit * @return string representation of entire matrix utilizing given strings */ public String toString(String setString, String unsetString) { return buildToString(setString, unsetString, "\n"); } /** * @param setString representation of a set bit * @param unsetString representation of an unset bit * @param lineSeparator newline character in string representation * @return string representation of entire matrix utilizing given strings and line separator * @deprecated call {@link #toString(String,String)} only, which uses \n line separator always */ @Deprecated public String toString(String setString, String unsetString, String lineSeparator) { return buildToString(setString, unsetString, lineSeparator); } private String buildToString(String setString, String unsetString, String lineSeparator) { StringBuilder result = new StringBuilder(height * (width + 1)); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { result.append(get(x, y) ? setString : unsetString); } result.append(lineSeparator); } return result.toString(); } @Override public BitMatrix clone() { return new BitMatrix(width, height, rowSize, bits.clone()); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/BitSource.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.common; /** *This provides an easy abstraction to read bits at a time from a sequence of bytes, where the * number of bits read is not often a multiple of 8.
* *This class is thread-safe but not reentrant -- unless the caller modifies the bytes array * it passed in, in which case all bets are off.
* * @author Sean Owen */ public final class BitSource { private final byte[] bytes; private int byteOffset; private int bitOffset; /** * @param bytes bytes from which this will read bits. Bits will be read from the first byte first. * Bits are read within a byte from most-significant to least-significant bit. */ public BitSource(byte[] bytes) { this.bytes = bytes; } /** * @return index of next bit in current byte which would be read by the next call to {@link #readBits(int)}. */ public int getBitOffset() { return bitOffset; } /** * @return index of next byte in input byte array which would be read by the next call to {@link #readBits(int)}. */ public int getByteOffset() { return byteOffset; } /** * @param numBits number of bits to read * @return int representing the bits read. The bits will appear as the least-significant * bits of the int * @throws IllegalArgumentException if numBits isn't in [1,32] or more than is available */ public int readBits(int numBits) { if (numBits < 1 || numBits > 32 || numBits > available()) { throw new IllegalArgumentException(String.valueOf(numBits)); } int result = 0; // First, read remainder from current byte if (bitOffset > 0) { int bitsLeft = 8 - bitOffset; int toRead = numBits < bitsLeft ? numBits : bitsLeft; int bitsToNotRead = bitsLeft - toRead; int mask = (0xFF >> (8 - toRead)) << bitsToNotRead; result = (bytes[byteOffset] & mask) >> bitsToNotRead; numBits -= toRead; bitOffset += toRead; if (bitOffset == 8) { bitOffset = 0; byteOffset++; } } // Next read whole bytes if (numBits > 0) { while (numBits >= 8) { result = (result << 8) | (bytes[byteOffset] & 0xFF); byteOffset++; numBits -= 8; } // Finally read a partial byte if (numBits > 0) { int bitsToNotRead = 8 - numBits; int mask = (0xFF >> bitsToNotRead) << bitsToNotRead; result = (result << numBits) | ((bytes[byteOffset] & mask) >> bitsToNotRead); bitOffset += numBits; } } return result; } /** * @return number of bits that can be read successfully */ public int available() { return 8 * (bytes.length - byteOffset) - bitOffset; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/CharacterSetECI.java ================================================ /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.common; import com.google.zxing.FormatException; import java.util.HashMap; import java.util.Map; /** * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 * of ISO 18004. * * @author Sean Owen */ public enum CharacterSetECI { // Enum name is a Java encoding valid for java.lang and java.io Cp437(new int[]{0,2}), ISO8859_1(new int[]{1,3}, "ISO-8859-1"), ISO8859_2(4, "ISO-8859-2"), ISO8859_3(5, "ISO-8859-3"), ISO8859_4(6, "ISO-8859-4"), ISO8859_5(7, "ISO-8859-5"), ISO8859_6(8, "ISO-8859-6"), ISO8859_7(9, "ISO-8859-7"), ISO8859_8(10, "ISO-8859-8"), ISO8859_9(11, "ISO-8859-9"), ISO8859_10(12, "ISO-8859-10"), ISO8859_11(13, "ISO-8859-11"), ISO8859_13(15, "ISO-8859-13"), ISO8859_14(16, "ISO-8859-14"), ISO8859_15(17, "ISO-8859-15"), ISO8859_16(18, "ISO-8859-16"), SJIS(20, "Shift_JIS"), Cp1250(21, "windows-1250"), Cp1251(22, "windows-1251"), Cp1252(23, "windows-1252"), Cp1256(24, "windows-1256"), UnicodeBigUnmarked(25, "UTF-16BE", "UnicodeBig"), UTF8(26, "UTF-8"), ASCII(new int[] {27, 170}, "US-ASCII"), Big5(28), GB18030(29, "GB2312", "EUC_CN", "GBK"), EUC_KR(30, "EUC-KR"); private static final MapEncapsulates the result of decoding a matrix of bits. This typically * applies to 2D barcode formats. For now it contains the raw bytes obtained, * as well as a String interpretation of those bytes, if applicable.
* * @author Sean Owen */ public final class DecoderResult { private final byte[] rawBytes; private int numBits; private final String text; private final ListEncapsulates the result of detecting a barcode in an image. This includes the raw * matrix of black/white pixels corresponding to the barcode, and possibly points of interest * in the image, like the location of finder patterns or corners of the barcode in the image.
* * @author Sean Owen */ public class DetectorResult { private final BitMatrix bits; private final ResultPoint[] points; public DetectorResult(BitMatrix bits, ResultPoint[] points) { this.bits = bits; this.points = points; } public final BitMatrix getBits() { return bits; } public final ResultPoint[] getPoints() { return points; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/GlobalHistogramBinarizer.java ================================================ /* * Copyright 2009 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.common; import com.google.zxing.Binarizer; import com.google.zxing.LuminanceSource; import com.google.zxing.NotFoundException; /** * This Binarizer implementation uses the old ZXing global histogram approach. It is suitable * for low-end mobile devices which don't have enough CPU or memory to use a local thresholding * algorithm. However, because it picks a global black point, it cannot handle difficult shadows * and gradients. * * Faster mobile devices and all desktop applications should probably use HybridBinarizer instead. * * @author dswitkin@google.com (Daniel Switkin) * @author Sean Owen */ public class GlobalHistogramBinarizer extends Binarizer { private static final int LUMINANCE_BITS = 5; private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; private static final byte[] EMPTY = new byte[0]; private byte[] luminances; private final int[] buckets; public GlobalHistogramBinarizer(LuminanceSource source) { super(source); luminances = EMPTY; buckets = new int[LUMINANCE_BUCKETS]; } // Applies simple sharpening to the row data to improve performance of the 1D Readers. @Override public BitArray getBlackRow(int y, BitArray row) throws NotFoundException { LuminanceSource source = getLuminanceSource(); int width = source.getWidth(); if (row == null || row.getSize() < width) { row = new BitArray(width); } else { row.clear(); } initArrays(width); byte[] localLuminances = source.getRow(y, luminances); int[] localBuckets = buckets; for (int x = 0; x < width; x++) { localBuckets[(localLuminances[x] & 0xff) >> LUMINANCE_SHIFT]++; } int blackPoint = estimateBlackPoint(localBuckets); if (width < 3) { // Special case for very small images for (int x = 0; x < width; x++) { if ((localLuminances[x] & 0xff) < blackPoint) { row.set(x); } } } else { int left = localLuminances[0] & 0xff; int center = localLuminances[1] & 0xff; for (int x = 1; x < width - 1; x++) { int right = localLuminances[x + 1] & 0xff; // A simple -1 4 -1 box filter with a weight of 2. if (((center * 4) - left - right) / 2 < blackPoint) { row.set(x); } left = center; center = right; } } return row; } // Does not sharpen the data, as this call is intended to only be used by 2D Readers. @Override public BitMatrix getBlackMatrix() throws NotFoundException { LuminanceSource source = getLuminanceSource(); int width = source.getWidth(); int height = source.getHeight(); BitMatrix matrix = new BitMatrix(width, height); // Quickly calculates the histogram by sampling four rows from the image. This proved to be // more robust on the blackbox tests than sampling a diagonal as we used to do. initArrays(width); int[] localBuckets = buckets; for (int y = 1; y < 5; y++) { int row = height * y / 5; byte[] localLuminances = source.getRow(row, luminances); int right = (width * 4) / 5; for (int x = width / 5; x < right; x++) { int pixel = localLuminances[x] & 0xff; localBuckets[pixel >> LUMINANCE_SHIFT]++; } } int blackPoint = estimateBlackPoint(localBuckets); // We delay reading the entire image luminance until the black point estimation succeeds. // Although we end up reading four rows twice, it is consistent with our motto of // "fail quickly" which is necessary for continuous scanning. byte[] localLuminances = source.getMatrix(); for (int y = 0; y < height; y++) { int offset = y * width; for (int x = 0; x < width; x++) { int pixel = localLuminances[offset + x] & 0xff; if (pixel < blackPoint) { matrix.set(x, y); } } } return matrix; } @Override public Binarizer createBinarizer(LuminanceSource source) { return new GlobalHistogramBinarizer(source); } private void initArrays(int luminanceSize) { if (luminances.length < luminanceSize) { luminances = new byte[luminanceSize]; } for (int x = 0; x < LUMINANCE_BUCKETS; x++) { buckets[x] = 0; } } private static int estimateBlackPoint(int[] buckets) throws NotFoundException { // Find the tallest peak in the histogram. int numBuckets = buckets.length; int maxBucketCount = 0; int firstPeak = 0; int firstPeakSize = 0; for (int x = 0; x < numBuckets; x++) { if (buckets[x] > firstPeakSize) { firstPeak = x; firstPeakSize = buckets[x]; } if (buckets[x] > maxBucketCount) { maxBucketCount = buckets[x]; } } // Find the second-tallest peak which is somewhat far from the tallest peak. int secondPeak = 0; int secondPeakScore = 0; for (int x = 0; x < numBuckets; x++) { int distanceToBiggest = x - firstPeak; // Encourage more distant second peaks by multiplying by square of distance. int score = buckets[x] * distanceToBiggest * distanceToBiggest; if (score > secondPeakScore) { secondPeak = x; secondPeakScore = score; } } // Make sure firstPeak corresponds to the black peak. if (firstPeak > secondPeak) { int temp = firstPeak; firstPeak = secondPeak; secondPeak = temp; } // If there is too little contrast in the image to pick a meaningful black point, throw rather // than waste time trying to decode the image, and risk false positives. if (secondPeak - firstPeak <= numBuckets / 16) { throw NotFoundException.getNotFoundInstance(); } // Find a valley between them that is low and closer to the white peak. int bestValley = secondPeak - 1; int bestValleyScore = -1; for (int x = secondPeak - 1; x > firstPeak; x--) { int fromFirst = x - firstPeak; int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]); if (score > bestValleyScore) { bestValley = x; bestValleyScore = score; } } return bestValley << LUMINANCE_SHIFT; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/GridSampler.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.common; import com.google.zxing.NotFoundException; /** * Implementations of this class can, given locations of finder patterns for a QR code in an * image, sample the right points in the image to reconstruct the QR code, accounting for * perspective distortion. It is abstracted since it is relatively expensive and should be allowed * to take advantage of platform-specific optimized implementations, like Sun's Java Advanced * Imaging library, but which may not be available in other environments such as J2ME, and vice * versa. * * The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)} * with an instance of a class which implements this interface. * * @author Sean Owen */ public abstract class GridSampler { private static GridSampler gridSampler = new DefaultGridSampler(); /** * Sets the implementation of GridSampler used by the library. One global * instance is stored, which may sound problematic. But, the implementation provided * ought to be appropriate for the entire platform, and all uses of this library * in the whole lifetime of the JVM. For instance, an Android activity can swap in * an implementation that takes advantage of native platform libraries. * * @param newGridSampler The platform-specific object to install. */ public static void setGridSampler(GridSampler newGridSampler) { gridSampler = newGridSampler; } /** * @return the current implementation of GridSampler */ public static GridSampler getInstance() { return gridSampler; } /** * Samples an image for a rectangular matrix of bits of the given dimension. The sampling * transformation is determined by the coordinates of 4 points, in the original and transformed * image space. * * @param image image to sample * @param dimensionX width of {@link BitMatrix} to sample from image * @param dimensionY height of {@link BitMatrix} to sample from image * @param p1ToX point 1 preimage X * @param p1ToY point 1 preimage Y * @param p2ToX point 2 preimage X * @param p2ToY point 2 preimage Y * @param p3ToX point 3 preimage X * @param p3ToY point 3 preimage Y * @param p4ToX point 4 preimage X * @param p4ToY point 4 preimage Y * @param p1FromX point 1 image X * @param p1FromY point 1 image Y * @param p2FromX point 2 image X * @param p2FromY point 2 image Y * @param p3FromX point 3 image X * @param p3FromY point 3 image Y * @param p4FromX point 4 image X * @param p4FromY point 4 image Y * @return {@link BitMatrix} representing a grid of points sampled from the image within a region * defined by the "from" parameters * @throws NotFoundException if image can't be sampled, for example, if the transformation defined * by the given points is invalid or results in sampling outside the image boundaries */ public abstract BitMatrix sampleGrid(BitMatrix image, int dimensionX, int dimensionY, float p1ToX, float p1ToY, float p2ToX, float p2ToY, float p3ToX, float p3ToY, float p4ToX, float p4ToY, float p1FromX, float p1FromY, float p2FromX, float p2FromY, float p3FromX, float p3FromY, float p4FromX, float p4FromY) throws NotFoundException; public abstract BitMatrix sampleGrid(BitMatrix image, int dimensionX, int dimensionY, PerspectiveTransform transform) throws NotFoundException; /** *Checks a set of points that have been transformed to sample points on an image against * the image's dimensions to see if the point are even within the image.
* *This method will actually "nudge" the endpoints back onto the image if they are found to be * barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder * patterns in an image where the QR Code runs all the way to the image border.
* *For efficiency, the method will check points from either end of the line until one is found * to be within the image. Because the set of points are assumed to be linear, this is valid.
* * @param image image into which the points should map * @param points actual points in x1,y1,...,xn,yn form * @throws NotFoundException if an endpoint is lies outside the image boundaries */ protected static void checkAndNudgePoints(BitMatrix image, float[] points) throws NotFoundException { int width = image.getWidth(); int height = image.getHeight(); // Check and nudge points from start until we see some that are OK: boolean nudged = true; int maxOffset = points.length - 1; // points.length must be even for (int offset = 0; offset < maxOffset && nudged; offset += 2) { int x = (int) points[offset]; int y = (int) points[offset + 1]; if (x < -1 || x > width || y < -1 || y > height) { throw NotFoundException.getNotFoundInstance(); } nudged = false; if (x == -1) { points[offset] = 0.0f; nudged = true; } else if (x == width) { points[offset] = width - 1; nudged = true; } if (y == -1) { points[offset + 1] = 0.0f; nudged = true; } else if (y == height) { points[offset + 1] = height - 1; nudged = true; } } // Check and nudge points from end: nudged = true; for (int offset = points.length - 2; offset >= 0 && nudged; offset -= 2) { int x = (int) points[offset]; int y = (int) points[offset + 1]; if (x < -1 || x > width || y < -1 || y > height) { throw NotFoundException.getNotFoundInstance(); } nudged = false; if (x == -1) { points[offset] = 0.0f; nudged = true; } else if (x == width) { points[offset] = width - 1; nudged = true; } if (y == -1) { points[offset + 1] = 0.0f; nudged = true; } else if (y == height) { points[offset + 1] = height - 1; nudged = true; } } } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/HybridBinarizer.java ================================================ /* * Copyright 2009 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.common; import com.google.zxing.Binarizer; import com.google.zxing.LuminanceSource; import com.google.zxing.NotFoundException; /** * This class implements a local thresholding algorithm, which while slower than the * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for * high frequency images of barcodes with black data on white backgrounds. For this application, * it does a much better job than a global blackpoint with severe shadows and gradients. * However it tends to produce artifacts on lower frequency images and is therefore not * a good general purpose binarizer for uses outside ZXing. * * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers, * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already * inherently local, and only fails for horizontal gradients. We can revisit that problem later, * but for now it was not a win to use local blocks for 1D. * * This Binarizer is the default for the unit tests and the recommended class for library users. * * @author dswitkin@google.com (Daniel Switkin) */ public final class HybridBinarizer extends GlobalHistogramBinarizer { // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. // So this is the smallest dimension in each axis we can accept. private static final int BLOCK_SIZE_POWER = 3; private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00 private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; // ...0011...11 private static final int MINIMUM_DIMENSION = BLOCK_SIZE * 5; private static final int MIN_DYNAMIC_RANGE = 24; private BitMatrix matrix; public HybridBinarizer(LuminanceSource source) { super(source); } /** * Calculates the final BitMatrix once for all requests. This could be called once from the * constructor instead, but there are some advantages to doing it lazily, such as making * profiling easier, and not doing heavy lifting when callers don't expect it. */ @Override public BitMatrix getBlackMatrix() throws NotFoundException { if (matrix != null) { return matrix; } LuminanceSource source = getLuminanceSource(); int width = source.getWidth(); int height = source.getHeight(); if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION) { byte[] luminances = source.getMatrix(); int subWidth = width >> BLOCK_SIZE_POWER; if ((width & BLOCK_SIZE_MASK) != 0) { subWidth++; } int subHeight = height >> BLOCK_SIZE_POWER; if ((height & BLOCK_SIZE_MASK) != 0) { subHeight++; } int[][] blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width, height); BitMatrix newMatrix = new BitMatrix(width, height); calculateThresholdForBlock(luminances, subWidth, subHeight, width, height, blackPoints, newMatrix); matrix = newMatrix; } else { // If the image is too small, fall back to the global histogram approach. matrix = super.getBlackMatrix(); } return matrix; } @Override public Binarizer createBinarizer(LuminanceSource source) { return new HybridBinarizer(source); } /** * For each block in the image, calculate the average black point using a 5x5 grid * of the blocks around it. Also handles the corner cases (fractional blocks are computed based * on the last pixels in the row/column which are also used in the previous block). */ private static void calculateThresholdForBlock(byte[] luminances, int subWidth, int subHeight, int width, int height, int[][] blackPoints, BitMatrix matrix) { int maxYOffset = height - BLOCK_SIZE; int maxXOffset = width - BLOCK_SIZE; for (int y = 0; y < subHeight; y++) { int yoffset = y << BLOCK_SIZE_POWER; if (yoffset > maxYOffset) { yoffset = maxYOffset; } int top = cap(y, 2, subHeight - 3); for (int x = 0; x < subWidth; x++) { int xoffset = x << BLOCK_SIZE_POWER; if (xoffset > maxXOffset) { xoffset = maxXOffset; } int left = cap(x, 2, subWidth - 3); int sum = 0; for (int z = -2; z <= 2; z++) { int[] blackRow = blackPoints[top + z]; sum += blackRow[left - 2] + blackRow[left - 1] + blackRow[left] + blackRow[left + 1] + blackRow[left + 2]; } int average = sum / 25; thresholdBlock(luminances, xoffset, yoffset, average, width, matrix); } } } private static int cap(int value, int min, int max) { return value < min ? min : value > max ? max : value; } /** * Applies a single threshold to a block of pixels. */ private static void thresholdBlock(byte[] luminances, int xoffset, int yoffset, int threshold, int stride, BitMatrix matrix) { for (int y = 0, offset = yoffset * stride + xoffset; y < BLOCK_SIZE; y++, offset += stride) { for (int x = 0; x < BLOCK_SIZE; x++) { // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0. if ((luminances[offset + x] & 0xFF) <= threshold) { matrix.set(xoffset + x, yoffset + y); } } } } /** * Calculates a single black point for each block of pixels and saves it away. * See the following thread for a discussion of this algorithm: * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 */ private static int[][] calculateBlackPoints(byte[] luminances, int subWidth, int subHeight, int width, int height) { int maxYOffset = height - BLOCK_SIZE; int maxXOffset = width - BLOCK_SIZE; int[][] blackPoints = new int[subHeight][subWidth]; for (int y = 0; y < subHeight; y++) { int yoffset = y << BLOCK_SIZE_POWER; if (yoffset > maxYOffset) { yoffset = maxYOffset; } for (int x = 0; x < subWidth; x++) { int xoffset = x << BLOCK_SIZE_POWER; if (xoffset > maxXOffset) { xoffset = maxXOffset; } int sum = 0; int min = 0xFF; int max = 0; for (int yy = 0, offset = yoffset * width + xoffset; yy < BLOCK_SIZE; yy++, offset += width) { for (int xx = 0; xx < BLOCK_SIZE; xx++) { int pixel = luminances[offset + xx] & 0xFF; sum += pixel; // still looking for good contrast if (pixel < min) { min = pixel; } if (pixel > max) { max = pixel; } } // short-circuit min/max tests once dynamic range is met if (max - min > MIN_DYNAMIC_RANGE) { // finish the rest of the rows quickly for (yy++, offset += width; yy < BLOCK_SIZE; yy++, offset += width) { for (int xx = 0; xx < BLOCK_SIZE; xx++) { sum += luminances[offset + xx] & 0xFF; } } } } // The default estimate is the average of the values in the block. int average = sum >> (BLOCK_SIZE_POWER * 2); if (max - min <= MIN_DYNAMIC_RANGE) { // If variation within the block is low, assume this is a block with only light or only // dark pixels. In that case we do not want to use the average, as it would divide this // low contrast area into black and white pixels, essentially creating data out of noise. // // The default assumption is that the block is light/background. Since no estimate for // the level of dark pixels exists locally, use half the min for the block. average = min / 2; if (y > 0 && x > 0) { // Correct the "white background" assumption for blocks that have neighbors by comparing // the pixels in this block to the previously calculated black points. This is based on // the fact that dark barcode symbology is always surrounded by some amount of light // background for which reasonable black point estimates were made. The bp estimated at // the boundaries is used for the interior. // The (min < bp) is arbitrary but works better than other heuristics that were tried. int averageNeighborBlackPoint = (blackPoints[y - 1][x] + (2 * blackPoints[y][x - 1]) + blackPoints[y - 1][x - 1]) / 4; if (min < averageNeighborBlackPoint) { average = averageNeighborBlackPoint; } } } blackPoints[y][x] = average; } } return blackPoints; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/PerspectiveTransform.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.common; /** *This class implements a perspective transform in two dimensions. Given four source and four * destination points, it will compute the transformation implied between them. The code is based * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.
* * @author Sean Owen */ public final class PerspectiveTransform { private final float a11; private final float a12; private final float a13; private final float a21; private final float a22; private final float a23; private final float a31; private final float a32; private final float a33; private PerspectiveTransform(float a11, float a21, float a31, float a12, float a22, float a32, float a13, float a23, float a33) { this.a11 = a11; this.a12 = a12; this.a13 = a13; this.a21 = a21; this.a22 = a22; this.a23 = a23; this.a31 = a31; this.a32 = a32; this.a33 = a33; } public static PerspectiveTransform quadrilateralToQuadrilateral(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float x0p, float y0p, float x1p, float y1p, float x2p, float y2p, float x3p, float y3p) { PerspectiveTransform qToS = quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3); PerspectiveTransform sToQ = squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); return sToQ.times(qToS); } public void transformPoints(float[] points) { float a11 = this.a11; float a12 = this.a12; float a13 = this.a13; float a21 = this.a21; float a22 = this.a22; float a23 = this.a23; float a31 = this.a31; float a32 = this.a32; float a33 = this.a33; int maxI = points.length - 1; // points.length must be even for (int i = 0; i < maxI; i += 2) { float x = points[i]; float y = points[i + 1]; float denominator = a13 * x + a23 * y + a33; points[i] = (a11 * x + a21 * y + a31) / denominator; points[i + 1] = (a12 * x + a22 * y + a32) / denominator; } } public void transformPoints(float[] xValues, float[] yValues) { int n = xValues.length; for (int i = 0; i < n; i++) { float x = xValues[i]; float y = yValues[i]; float denominator = a13 * x + a23 * y + a33; xValues[i] = (a11 * x + a21 * y + a31) / denominator; yValues[i] = (a12 * x + a22 * y + a32) / denominator; } } public static PerspectiveTransform squareToQuadrilateral(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) { float dx3 = x0 - x1 + x2 - x3; float dy3 = y0 - y1 + y2 - y3; if (dx3 == 0.0f && dy3 == 0.0f) { // Affine return new PerspectiveTransform(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0.0f, 0.0f, 1.0f); } else { float dx1 = x1 - x2; float dx2 = x3 - x2; float dy1 = y1 - y2; float dy2 = y3 - y2; float denominator = dx1 * dy2 - dx2 * dy1; float a13 = (dx3 * dy2 - dx2 * dy3) / denominator; float a23 = (dx1 * dy3 - dx3 * dy1) / denominator; return new PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0, y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0, a13, a23, 1.0f); } } public static PerspectiveTransform quadrilateralToSquare(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) { // Here, the adjoint serves as the inverse: return squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint(); } PerspectiveTransform buildAdjoint() { // Adjoint is the transpose of the cofactor matrix: return new PerspectiveTransform(a22 * a33 - a23 * a32, a23 * a31 - a21 * a33, a21 * a32 - a22 * a31, a13 * a32 - a12 * a33, a11 * a33 - a13 * a31, a12 * a31 - a11 * a32, a12 * a23 - a13 * a22, a13 * a21 - a11 * a23, a11 * a22 - a12 * a21); } PerspectiveTransform times(PerspectiveTransform other) { return new PerspectiveTransform(a11 * other.a11 + a21 * other.a12 + a31 * other.a13, a11 * other.a21 + a21 * other.a22 + a31 * other.a23, a11 * other.a31 + a21 * other.a32 + a31 * other.a33, a12 * other.a11 + a22 * other.a12 + a32 * other.a13, a12 * other.a21 + a22 * other.a22 + a32 * other.a23, a12 * other.a31 + a22 * other.a32 + a32 * other.a33, a13 * other.a11 + a23 * other.a12 + a33 * other.a13, a13 * other.a21 + a23 * other.a22 + a33 * other.a23, a13 * other.a31 + a23 * other.a32 + a33 * other.a33); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/StringUtils.java ================================================ /* * Copyright (C) 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.common; import java.nio.charset.Charset; import java.util.Map; import com.google.zxing.DecodeHintType; /** * Common string-related functions. * * @author Sean Owen * @author Alex Dupre */ public final class StringUtils { private static final String PLATFORM_DEFAULT_ENCODING = Charset.defaultCharset().name(); public static final String SHIFT_JIS = "SJIS"; public static final String GB2312 = "GB2312"; private static final String EUC_JP = "EUC_JP"; private static final String UTF8 = "UTF8"; private static final String ISO88591 = "ISO8859_1"; private static final boolean ASSUME_SHIFT_JIS = SHIFT_JIS.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING) || EUC_JP.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING); private StringUtils() { } /** * @param bytes bytes encoding a string, whose encoding should be guessed * @param hints decode hints if applicable * @return name of guessed encoding; at the moment will only guess one of: * {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform * default encoding if none of these can possibly be correct */ public static String guessEncoding(byte[] bytes, MapA somewhat generic detector that looks for a barcode-like rectangular region within an image. * It looks within a mostly white region of an image for a region of black and white, but mostly * black. It returns the four corners of the region, as best it can determine.
* * @author Sean Owen * @deprecated without replacement since 3.3.0 */ @Deprecated public final class MonochromeRectangleDetector { private static final int MAX_MODULES = 32; private final BitMatrix image; public MonochromeRectangleDetector(BitMatrix image) { this.image = image; } /** *Detects a rectangular region of black and white -- mostly black -- with a region of mostly * white, in an image.
* * @return {@link ResultPoint}[] describing the corners of the rectangular region. The first and * last points are opposed on the diagonal, as are the second and third. The first point will be * the topmost point and the last, the bottommost. The second point will be leftmost and the * third, the rightmost * @throws NotFoundException if no Data Matrix Code can be found */ public ResultPoint[] detect() throws NotFoundException { int height = image.getHeight(); int width = image.getWidth(); int halfHeight = height / 2; int halfWidth = width / 2; int deltaY = Math.max(1, height / (MAX_MODULES * 8)); int deltaX = Math.max(1, width / (MAX_MODULES * 8)); int top = 0; int bottom = height; int left = 0; int right = width; ResultPoint pointA = findCornerFromCenter(halfWidth, 0, left, right, halfHeight, -deltaY, top, bottom, halfWidth / 2); top = (int) pointA.getY() - 1; ResultPoint pointB = findCornerFromCenter(halfWidth, -deltaX, left, right, halfHeight, 0, top, bottom, halfHeight / 2); left = (int) pointB.getX() - 1; ResultPoint pointC = findCornerFromCenter(halfWidth, deltaX, left, right, halfHeight, 0, top, bottom, halfHeight / 2); right = (int) pointC.getX() + 1; ResultPoint pointD = findCornerFromCenter(halfWidth, 0, left, right, halfHeight, deltaY, top, bottom, halfWidth / 2); bottom = (int) pointD.getY() + 1; // Go try to find point A again with better information -- might have been off at first. pointA = findCornerFromCenter(halfWidth, 0, left, right, halfHeight, -deltaY, top, bottom, halfWidth / 4); return new ResultPoint[] { pointA, pointB, pointC, pointD }; } /** * Attempts to locate a corner of the barcode by scanning up, down, left or right from a center * point which should be within the barcode. * * @param centerX center's x component (horizontal) * @param deltaX same as deltaY but change in x per step instead * @param left minimum value of x * @param right maximum value of x * @param centerY center's y component (vertical) * @param deltaY change in y per step. If scanning up this is negative; down, positive; * left or right, 0 * @param top minimum value of y to search through (meaningless when di == 0) * @param bottom maximum value of y * @param maxWhiteRun maximum run of white pixels that can still be considered to be within * the barcode * @return a {@link ResultPoint} encapsulating the corner that was found * @throws NotFoundException if such a point cannot be found */ private ResultPoint findCornerFromCenter(int centerX, int deltaX, int left, int right, int centerY, int deltaY, int top, int bottom, int maxWhiteRun) throws NotFoundException { int[] lastRange = null; for (int y = centerY, x = centerX; y < bottom && y >= top && x < right && x >= left; y += deltaY, x += deltaX) { int[] range; if (deltaX == 0) { // horizontal slices, up and down range = blackWhiteRange(y, maxWhiteRun, left, right, true); } else { // vertical slices, left and right range = blackWhiteRange(x, maxWhiteRun, top, bottom, false); } if (range == null) { if (lastRange == null) { throw NotFoundException.getNotFoundInstance(); } // lastRange was found if (deltaX == 0) { int lastY = y - deltaY; if (lastRange[0] < centerX) { if (lastRange[1] > centerX) { // straddle, choose one or the other based on direction return new ResultPoint(lastRange[deltaY > 0 ? 0 : 1], lastY); } return new ResultPoint(lastRange[0], lastY); } else { return new ResultPoint(lastRange[1], lastY); } } else { int lastX = x - deltaX; if (lastRange[0] < centerY) { if (lastRange[1] > centerY) { return new ResultPoint(lastX, lastRange[deltaX < 0 ? 0 : 1]); } return new ResultPoint(lastX, lastRange[0]); } else { return new ResultPoint(lastX, lastRange[1]); } } } lastRange = range; } throw NotFoundException.getNotFoundInstance(); } /** * Computes the start and end of a region of pixels, either horizontally or vertically, that could * be part of a Data Matrix barcode. * * @param fixedDimension if scanning horizontally, this is the row (the fixed vertical location) * where we are scanning. If scanning vertically it's the column, the fixed horizontal location * @param maxWhiteRun largest run of white pixels that can still be considered part of the * barcode region * @param minDim minimum pixel location, horizontally or vertically, to consider * @param maxDim maximum pixel location, horizontally or vertically, to consider * @param horizontal if true, we're scanning left-right, instead of up-down * @return int[] with start and end of found range, or null if no such range is found * (e.g. only white was found) */ private int[] blackWhiteRange(int fixedDimension, int maxWhiteRun, int minDim, int maxDim, boolean horizontal) { int center = (minDim + maxDim) / 2; // Scan left/up first int start = center; while (start >= minDim) { if (horizontal ? image.get(start, fixedDimension) : image.get(fixedDimension, start)) { start--; } else { int whiteRunStart = start; do { start--; } while (start >= minDim && !(horizontal ? image.get(start, fixedDimension) : image.get(fixedDimension, start))); int whiteRunSize = whiteRunStart - start; if (start < minDim || whiteRunSize > maxWhiteRun) { start = whiteRunStart; break; } } } start++; // Then try right/down int end = center; while (end < maxDim) { if (horizontal ? image.get(end, fixedDimension) : image.get(fixedDimension, end)) { end++; } else { int whiteRunStart = end; do { end++; } while (end < maxDim && !(horizontal ? image.get(end, fixedDimension) : image.get(fixedDimension, end))); int whiteRunSize = end - whiteRunStart; if (end >= maxDim || whiteRunSize > maxWhiteRun) { end = whiteRunStart; break; } } } end--; return end > start ? new int[]{start, end} : null; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/detector/WhiteRectangleDetector.java ================================================ /* * 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.common.detector; import com.google.zxing.NotFoundException; import com.google.zxing.ResultPoint; import com.google.zxing.common.BitMatrix; /** ** Detects a candidate barcode-like rectangular region within an image. It * starts around the center of the image, increases the size of the candidate * region until it finds a white rectangular region. By keeping track of the * last black points it encountered, it determines the corners of the barcode. *
* * @author David Olivier */ public final class WhiteRectangleDetector { private static final int INIT_SIZE = 10; private static final int CORR = 1; private final BitMatrix image; private final int height; private final int width; private final int leftInit; private final int rightInit; private final int downInit; private final int upInit; public WhiteRectangleDetector(BitMatrix image) throws NotFoundException { this(image, INIT_SIZE, image.getWidth() / 2, image.getHeight() / 2); } /** * @param image barcode image to find a rectangle in * @param initSize initial size of search area around center * @param x x position of search center * @param y y position of search center * @throws NotFoundException if image is too small to accommodate {@code initSize} */ public WhiteRectangleDetector(BitMatrix image, int initSize, int x, int y) throws NotFoundException { this.image = image; height = image.getHeight(); width = image.getWidth(); int halfsize = initSize / 2; leftInit = x - halfsize; rightInit = x + halfsize; upInit = y - halfsize; downInit = y + halfsize; if (upInit < 0 || leftInit < 0 || downInit >= height || rightInit >= width) { throw NotFoundException.getNotFoundInstance(); } } /** ** Detects a candidate barcode-like rectangular region within an image. It * starts around the center of the image, increases the size of the candidate * region until it finds a white rectangular region. *
* * @return {@link ResultPoint}[] describing the corners of the rectangular * region. The first and last points are opposed on the diagonal, as * are the second and third. The first point will be the topmost * point and the last, the bottommost. The second point will be * leftmost and the third, the rightmost * @throws NotFoundException if no Data Matrix Code can be found */ public ResultPoint[] detect() throws NotFoundException { int left = leftInit; int right = rightInit; int up = upInit; int down = downInit; boolean sizeExceeded = false; boolean aBlackPointFoundOnBorder = true; boolean atLeastOneBlackPointFoundOnRight = false; boolean atLeastOneBlackPointFoundOnBottom = false; boolean atLeastOneBlackPointFoundOnLeft = false; boolean atLeastOneBlackPointFoundOnTop = false; while (aBlackPointFoundOnBorder) { aBlackPointFoundOnBorder = false; // ..... // . | // ..... boolean rightBorderNotWhite = true; while ((rightBorderNotWhite || !atLeastOneBlackPointFoundOnRight) && right < width) { rightBorderNotWhite = containsBlackPoint(up, down, right, false); if (rightBorderNotWhite) { right++; aBlackPointFoundOnBorder = true; atLeastOneBlackPointFoundOnRight = true; } else if (!atLeastOneBlackPointFoundOnRight) { right++; } } if (right >= width) { sizeExceeded = true; break; } // ..... // . . // .___. boolean bottomBorderNotWhite = true; while ((bottomBorderNotWhite || !atLeastOneBlackPointFoundOnBottom) && down < height) { bottomBorderNotWhite = containsBlackPoint(left, right, down, true); if (bottomBorderNotWhite) { down++; aBlackPointFoundOnBorder = true; atLeastOneBlackPointFoundOnBottom = true; } else if (!atLeastOneBlackPointFoundOnBottom) { down++; } } if (down >= height) { sizeExceeded = true; break; } // ..... // | . // ..... boolean leftBorderNotWhite = true; while ((leftBorderNotWhite || !atLeastOneBlackPointFoundOnLeft) && left >= 0) { leftBorderNotWhite = containsBlackPoint(up, down, left, false); if (leftBorderNotWhite) { left--; aBlackPointFoundOnBorder = true; atLeastOneBlackPointFoundOnLeft = true; } else if (!atLeastOneBlackPointFoundOnLeft) { left--; } } if (left < 0) { sizeExceeded = true; break; } // .___. // . . // ..... boolean topBorderNotWhite = true; while ((topBorderNotWhite || !atLeastOneBlackPointFoundOnTop) && up >= 0) { topBorderNotWhite = containsBlackPoint(left, right, up, true); if (topBorderNotWhite) { up--; aBlackPointFoundOnBorder = true; atLeastOneBlackPointFoundOnTop = true; } else if (!atLeastOneBlackPointFoundOnTop) { up--; } } if (up < 0) { sizeExceeded = true; break; } } if (!sizeExceeded) { int maxSize = right - left; ResultPoint z = null; for (int i = 1; z == null && i < maxSize; i++) { z = getBlackPointOnSegment(left, down - i, left + i, down); } if (z == null) { throw NotFoundException.getNotFoundInstance(); } ResultPoint t = null; //go down right for (int i = 1; t == null && i < maxSize; i++) { t = getBlackPointOnSegment(left, up + i, left + i, up); } if (t == null) { throw NotFoundException.getNotFoundInstance(); } ResultPoint x = null; //go down left for (int i = 1; x == null && i < maxSize; i++) { x = getBlackPointOnSegment(right, up + i, right - i, up); } if (x == null) { throw NotFoundException.getNotFoundInstance(); } ResultPoint y = null; //go up left for (int i = 1; y == null && i < maxSize; i++) { y = getBlackPointOnSegment(right, down - i, right - i, down); } if (y == null) { throw NotFoundException.getNotFoundInstance(); } return centerEdges(y, z, x, t); } else { throw NotFoundException.getNotFoundInstance(); } } private ResultPoint getBlackPointOnSegment(float aX, float aY, float bX, float bY) { int dist = MathUtils.round(MathUtils.distance(aX, aY, bX, bY)); float xStep = (bX - aX) / dist; float yStep = (bY - aY) / dist; for (int i = 0; i < dist; i++) { int x = MathUtils.round(aX + i * xStep); int y = MathUtils.round(aY + i * yStep); if (image.get(x, y)) { return new ResultPoint(x, y); } } return null; } /** * recenters the points of a constant distance towards the center * * @param y bottom most point * @param z left most point * @param x right most point * @param t top most point * @return {@link ResultPoint}[] describing the corners of the rectangular * region. The first and last points are opposed on the diagonal, as * are the second and third. The first point will be the topmost * point and the last, the bottommost. The second point will be * leftmost and the third, the rightmost */ private ResultPoint[] centerEdges(ResultPoint y, ResultPoint z, ResultPoint x, ResultPoint t) { // // t t // z x // x OR z // y y // float yi = y.getX(); float yj = y.getY(); float zi = z.getX(); float zj = z.getY(); float xi = x.getX(); float xj = x.getY(); float ti = t.getX(); float tj = t.getY(); if (yi < width / 2.0f) { return new ResultPoint[]{ new ResultPoint(ti - CORR, tj + CORR), new ResultPoint(zi + CORR, zj + CORR), new ResultPoint(xi - CORR, xj - CORR), new ResultPoint(yi + CORR, yj - CORR)}; } else { return new ResultPoint[]{ new ResultPoint(ti + CORR, tj + CORR), new ResultPoint(zi + CORR, zj - CORR), new ResultPoint(xi - CORR, xj + CORR), new ResultPoint(yi - CORR, yj - CORR)}; } } /** * Determines whether a segment contains a black point * * @param a min value of the scanned coordinate * @param b max value of the scanned coordinate * @param fixed value of fixed coordinate * @param horizontal set to true if scan must be horizontal, false if vertical * @return true if a black point has been found, else false. */ private boolean containsBlackPoint(int a, int b, int fixed, boolean horizontal) { if (horizontal) { for (int x = a; x <= b; x++) { if (image.get(x, fixed)) { return true; } } } else { for (int y = a; y <= b; y++) { if (image.get(fixed, y)) { return true; } } } return false; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/reedsolomon/GenericGF.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.common.reedsolomon; /** *This class contains utility methods for performing mathematical operations over * the Galois Fields. Operations use a given primitive polynomial in calculations.
* *Throughout this package, elements of the GF are represented as an {@code int} * for convenience and speed (but at the cost of memory). *
* * @author Sean Owen * @author David Olivier */ public final class GenericGF { public static final GenericGF AZTEC_DATA_12 = new GenericGF(0x1069, 4096, 1); // x^12 + x^6 + x^5 + x^3 + 1 public static final GenericGF AZTEC_DATA_10 = new GenericGF(0x409, 1024, 1); // x^10 + x^3 + 1 public static final GenericGF AZTEC_DATA_6 = new GenericGF(0x43, 64, 1); // x^6 + x + 1 public static final GenericGF AZTEC_PARAM = new GenericGF(0x13, 16, 1); // x^4 + x + 1 public static final GenericGF QR_CODE_FIELD_256 = new GenericGF(0x011D, 256, 0); // x^8 + x^4 + x^3 + x^2 + 1 public static final GenericGF DATA_MATRIX_FIELD_256 = new GenericGF(0x012D, 256, 1); // x^8 + x^5 + x^3 + x^2 + 1 public static final GenericGF AZTEC_DATA_8 = DATA_MATRIX_FIELD_256; public static final GenericGF MAXICODE_FIELD_64 = AZTEC_DATA_6; private final int[] expTable; private final int[] logTable; private final GenericGFPoly zero; private final GenericGFPoly one; private final int size; private final int primitive; private final int generatorBase; /** * Create a representation of GF(size) using the given primitive polynomial. * * @param primitive irreducible polynomial whose coefficients are represented by * the bits of an int, where the least-significant bit represents the constant * coefficient * @param size the size of the field * @param b the factor b in the generator polynomial can be 0- or 1-based * (g(x) = (x+a^b)(x+a^(b+1))...(x+a^(b+2t-1))). * In most cases it should be 1, but for QR code it is 0. */ public GenericGF(int primitive, int size, int b) { this.primitive = primitive; this.size = size; this.generatorBase = b; expTable = new int[size]; logTable = new int[size]; int x = 1; for (int i = 0; i < size; i++) { expTable[i] = x; x *= 2; // we're assuming the generator alpha is 2 if (x >= size) { x ^= primitive; x &= size - 1; } } for (int i = 0; i < size - 1; i++) { logTable[expTable[i]] = i; } // logTable[0] == 0 but this should never be used zero = new GenericGFPoly(this, new int[]{0}); one = new GenericGFPoly(this, new int[]{1}); } GenericGFPoly getZero() { return zero; } GenericGFPoly getOne() { return one; } /** * @return the monomial representing coefficient * x^degree */ GenericGFPoly buildMonomial(int degree, int coefficient) { if (degree < 0) { throw new IllegalArgumentException(); } if (coefficient == 0) { return zero; } int[] coefficients = new int[degree + 1]; coefficients[0] = coefficient; return new GenericGFPoly(this, coefficients); } /** * Implements both addition and subtraction -- they are the same in GF(size). * * @return sum/difference of a and b */ static int addOrSubtract(int a, int b) { return a ^ b; } /** * @return 2 to the power of a in GF(size) */ int exp(int a) { return expTable[a]; } /** * @return base 2 log of a in GF(size) */ int log(int a) { if (a == 0) { throw new IllegalArgumentException(); } return logTable[a]; } /** * @return multiplicative inverse of a */ int inverse(int a) { if (a == 0) { throw new ArithmeticException(); } return expTable[size - logTable[a] - 1]; } /** * @return product of a and b in GF(size) */ int multiply(int a, int b) { if (a == 0 || b == 0) { return 0; } return expTable[(logTable[a] + logTable[b]) % (size - 1)]; } public int getSize() { return size; } public int getGeneratorBase() { return generatorBase; } @Override public String toString() { return "GF(0x" + Integer.toHexString(primitive) + ',' + size + ')'; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/reedsolomon/GenericGFPoly.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.common.reedsolomon; /** *Represents a polynomial whose coefficients are elements of a GF. * Instances of this class are immutable.
* *Much credit is due to William Rucklidge since portions of this code are an indirect * port of his C++ Reed-Solomon implementation.
* * @author Sean Owen */ final class GenericGFPoly { private final GenericGF field; private final int[] coefficients; /** * @param field the {@link GenericGF} instance representing the field to use * to perform computations * @param coefficients coefficients as ints representing elements of GF(size), arranged * from most significant (highest-power term) coefficient to least significant * @throws IllegalArgumentException if argument is null or empty, * or if leading coefficient is 0 and this is not a * constant polynomial (that is, it is not the monomial "0") */ GenericGFPoly(GenericGF field, int[] coefficients) { if (coefficients.length == 0) { throw new IllegalArgumentException(); } this.field = field; int coefficientsLength = coefficients.length; if (coefficientsLength > 1 && coefficients[0] == 0) { // Leading term must be non-zero for anything except the constant polynomial "0" int firstNonZero = 1; while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) { firstNonZero++; } if (firstNonZero == coefficientsLength) { this.coefficients = new int[]{0}; } else { this.coefficients = new int[coefficientsLength - firstNonZero]; System.arraycopy(coefficients, firstNonZero, this.coefficients, 0, this.coefficients.length); } } else { this.coefficients = coefficients; } } int[] getCoefficients() { return coefficients; } /** * @return degree of this polynomial */ int getDegree() { return coefficients.length - 1; } /** * @return true iff this polynomial is the monomial "0" */ boolean isZero() { return coefficients[0] == 0; } /** * @return coefficient of x^degree term in this polynomial */ int getCoefficient(int degree) { return coefficients[coefficients.length - 1 - degree]; } /** * @return evaluation of this polynomial at a given point */ int evaluateAt(int a) { if (a == 0) { // Just return the x^0 coefficient return getCoefficient(0); } if (a == 1) { // Just the sum of the coefficients int result = 0; for (int coefficient : coefficients) { result = GenericGF.addOrSubtract(result, coefficient); } return result; } int result = coefficients[0]; int size = coefficients.length; for (int i = 1; i < size; i++) { result = GenericGF.addOrSubtract(field.multiply(a, result), coefficients[i]); } return result; } GenericGFPoly addOrSubtract(GenericGFPoly other) { if (!field.equals(other.field)) { throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field"); } if (isZero()) { return other; } if (other.isZero()) { return this; } int[] smallerCoefficients = this.coefficients; int[] largerCoefficients = other.coefficients; if (smallerCoefficients.length > largerCoefficients.length) { int[] temp = smallerCoefficients; smallerCoefficients = largerCoefficients; largerCoefficients = temp; } int[] sumDiff = new int[largerCoefficients.length]; int lengthDiff = largerCoefficients.length - smallerCoefficients.length; // Copy high-order terms only found in higher-degree polynomial's coefficients System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff); for (int i = lengthDiff; i < largerCoefficients.length; i++) { sumDiff[i] = GenericGF.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); } return new GenericGFPoly(field, sumDiff); } GenericGFPoly multiply(GenericGFPoly other) { if (!field.equals(other.field)) { throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field"); } if (isZero() || other.isZero()) { return field.getZero(); } int[] aCoefficients = this.coefficients; int aLength = aCoefficients.length; int[] bCoefficients = other.coefficients; int bLength = bCoefficients.length; int[] product = new int[aLength + bLength - 1]; for (int i = 0; i < aLength; i++) { int aCoeff = aCoefficients[i]; for (int j = 0; j < bLength; j++) { product[i + j] = GenericGF.addOrSubtract(product[i + j], field.multiply(aCoeff, bCoefficients[j])); } } return new GenericGFPoly(field, product); } GenericGFPoly multiply(int scalar) { if (scalar == 0) { return field.getZero(); } if (scalar == 1) { return this; } int size = coefficients.length; int[] product = new int[size]; for (int i = 0; i < size; i++) { product[i] = field.multiply(coefficients[i], scalar); } return new GenericGFPoly(field, product); } GenericGFPoly multiplyByMonomial(int degree, int coefficient) { if (degree < 0) { throw new IllegalArgumentException(); } if (coefficient == 0) { return field.getZero(); } int size = coefficients.length; int[] product = new int[size + degree]; for (int i = 0; i < size; i++) { product[i] = field.multiply(coefficients[i], coefficient); } return new GenericGFPoly(field, product); } GenericGFPoly[] divide(GenericGFPoly other) { if (!field.equals(other.field)) { throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field"); } if (other.isZero()) { throw new IllegalArgumentException("Divide by 0"); } GenericGFPoly quotient = field.getZero(); GenericGFPoly remainder = this; int denominatorLeadingTerm = other.getCoefficient(other.getDegree()); int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm); while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) { int degreeDifference = remainder.getDegree() - other.getDegree(); int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm); GenericGFPoly term = other.multiplyByMonomial(degreeDifference, scale); GenericGFPoly iterationQuotient = field.buildMonomial(degreeDifference, scale); quotient = quotient.addOrSubtract(iterationQuotient); remainder = remainder.addOrSubtract(term); } return new GenericGFPoly[] { quotient, remainder }; } @Override public String toString() { if (isZero()) { return "0"; } StringBuilder result = new StringBuilder(8 * getDegree()); for (int degree = getDegree(); degree >= 0; degree--) { int coefficient = getCoefficient(degree); if (coefficient != 0) { if (coefficient < 0) { if (degree == getDegree()) { result.append("-"); } else { result.append(" - "); } coefficient = -coefficient; } else { if (result.length() > 0) { result.append(" + "); } } if (degree == 0 || coefficient != 1) { int alphaPower = field.log(coefficient); if (alphaPower == 0) { result.append('1'); } else if (alphaPower == 1) { result.append('a'); } else { result.append("a^"); result.append(alphaPower); } } if (degree != 0) { if (degree == 1) { result.append('x'); } else { result.append("x^"); result.append(degree); } } } } return result.toString(); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.common.reedsolomon; /** *Implements Reed-Solomon decoding, as the name implies.
* *The algorithm will not be explained here, but the following references were helpful * in creating this implementation:
* *Much credit is due to William Rucklidge since portions of this code are an indirect * port of his C++ Reed-Solomon implementation.
* * @author Sean Owen * @author William Rucklidge * @author sanfordsquires */ public final class ReedSolomonDecoder { private final GenericGF field; public ReedSolomonDecoder(GenericGF field) { this.field = field; } /** *Decodes given set of received codewords, which include both data and error-correction * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place, * in the input.
* * @param received data and error-correction codewords * @param twoS number of error-correction codewords available * @throws ReedSolomonException if decoding fails for any reason */ public void decode(int[] received, int twoS) throws ReedSolomonException { GenericGFPoly poly = new GenericGFPoly(field, received); int[] syndromeCoefficients = new int[twoS]; boolean noError = true; for (int i = 0; i < twoS; i++) { int eval = poly.evaluateAt(field.exp(i + field.getGeneratorBase())); syndromeCoefficients[syndromeCoefficients.length - 1 - i] = eval; if (eval != 0) { noError = false; } } if (noError) { return; } GenericGFPoly syndrome = new GenericGFPoly(field, syndromeCoefficients); GenericGFPoly[] sigmaOmega = runEuclideanAlgorithm(field.buildMonomial(twoS, 1), syndrome, twoS); GenericGFPoly sigma = sigmaOmega[0]; GenericGFPoly omega = sigmaOmega[1]; int[] errorLocations = findErrorLocations(sigma); int[] errorMagnitudes = findErrorMagnitudes(omega, errorLocations); for (int i = 0; i < errorLocations.length; i++) { int position = received.length - 1 - field.log(errorLocations[i]); if (position < 0) { throw new ReedSolomonException("Bad error location"); } received[position] = GenericGF.addOrSubtract(received[position], errorMagnitudes[i]); } } private GenericGFPoly[] runEuclideanAlgorithm(GenericGFPoly a, GenericGFPoly b, int R) throws ReedSolomonException { // Assume a's degree is >= b's if (a.getDegree() < b.getDegree()) { GenericGFPoly temp = a; a = b; b = temp; } GenericGFPoly rLast = a; GenericGFPoly r = b; GenericGFPoly tLast = field.getZero(); GenericGFPoly t = field.getOne(); // Run Euclidean algorithm until r's degree is less than R/2 while (r.getDegree() >= R / 2) { GenericGFPoly rLastLast = rLast; GenericGFPoly tLastLast = tLast; rLast = r; tLast = t; // Divide rLastLast by rLast, with quotient in q and remainder in r if (rLast.isZero()) { // Oops, Euclidean algorithm already terminated? throw new ReedSolomonException("r_{i-1} was zero"); } r = rLastLast; GenericGFPoly q = field.getZero(); int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree()); int dltInverse = field.inverse(denominatorLeadingTerm); while (r.getDegree() >= rLast.getDegree() && !r.isZero()) { int degreeDiff = r.getDegree() - rLast.getDegree(); int scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse); q = q.addOrSubtract(field.buildMonomial(degreeDiff, scale)); r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale)); } t = q.multiply(tLast).addOrSubtract(tLastLast); if (r.getDegree() >= rLast.getDegree()) { throw new IllegalStateException("Division algorithm failed to reduce polynomial?"); } } int sigmaTildeAtZero = t.getCoefficient(0); if (sigmaTildeAtZero == 0) { throw new ReedSolomonException("sigmaTilde(0) was zero"); } int inverse = field.inverse(sigmaTildeAtZero); GenericGFPoly sigma = t.multiply(inverse); GenericGFPoly omega = r.multiply(inverse); return new GenericGFPoly[]{sigma, omega}; } private int[] findErrorLocations(GenericGFPoly errorLocator) throws ReedSolomonException { // This is a direct application of Chien's search int numErrors = errorLocator.getDegree(); if (numErrors == 1) { // shortcut return new int[] { errorLocator.getCoefficient(1) }; } int[] result = new int[numErrors]; int e = 0; for (int i = 1; i < field.getSize() && e < numErrors; i++) { if (errorLocator.evaluateAt(i) == 0) { result[e] = field.inverse(i); e++; } } if (e != numErrors) { throw new ReedSolomonException("Error locator degree does not match number of roots"); } return result; } private int[] findErrorMagnitudes(GenericGFPoly errorEvaluator, int[] errorLocations) { // This is directly applying Forney's Formula int s = errorLocations.length; int[] result = new int[s]; for (int i = 0; i < s; i++) { int xiInverse = field.inverse(errorLocations[i]); int denominator = 1; for (int j = 0; j < s; j++) { if (i != j) { //denominator = field.multiply(denominator, // GenericGF.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse))); // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug. // Below is a funny-looking workaround from Steven Parkes int term = field.multiply(errorLocations[j], xiInverse); int termPlus1 = (term & 0x1) == 0 ? term | 1 : term & ~1; denominator = field.multiply(denominator, termPlus1); } } result[i] = field.multiply(errorEvaluator.evaluateAt(xiInverse), field.inverse(denominator)); if (field.getGeneratorBase() != 0) { result[i] = field.multiply(result[i], xiInverse); } } return result; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java ================================================ /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.common.reedsolomon; import java.util.ArrayList; import java.util.List; /** *Implements Reed-Solomon encoding, as the name implies.
* * @author Sean Owen * @author William Rucklidge */ public final class ReedSolomonEncoder { private final GenericGF field; private final ListThrown when an exception occurs during Reed-Solomon decoding, such as when * there are too many errors to correct.
* * @author Sean Owen */ public final class ReedSolomonException extends Exception { public ReedSolomonException(String message) { super(message); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/DataMatrixReader.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.datamatrix; import com.google.zxing.BarcodeFormat; import com.google.zxing.BinaryBitmap; import com.google.zxing.ChecksumException; import com.google.zxing.DecodeHintType; import com.google.zxing.FormatException; import com.google.zxing.NotFoundException; import com.google.zxing.Reader; import com.google.zxing.Result; import com.google.zxing.ResultMetadataType; import com.google.zxing.ResultPoint; import com.google.zxing.common.BitMatrix; import com.google.zxing.common.DecoderResult; import com.google.zxing.common.DetectorResult; import com.google.zxing.datamatrix.decoder.Decoder; import com.google.zxing.datamatrix.detector.Detector; import java.util.List; import java.util.Map; /** * This implementation can detect and decode Data Matrix codes in an image. * * @author bbrown@google.com (Brian Brown) */ public final class DataMatrixReader implements Reader { private static final ResultPoint[] NO_POINTS = new ResultPoint[0]; private final Decoder decoder = new Decoder(); /** * 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 * @throws ChecksumException if error correction fails */ @Override public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException { return decode(image, null); } @Override public Result decode(BinaryBitmap image, MapCreates the version object based on the dimension of the original bit matrix from * the datamatrix code.
* *See ISO 16022:2006 Table 7 - ECC 200 symbol attributes
* * @param bitMatrix Original {@link BitMatrix} including alignment patterns * @return {@link Version} encapsulating the Data Matrix Code's "version" * @throws FormatException if the dimensions of the mapping matrix are not valid * Data Matrix dimensions. */ private static Version readVersion(BitMatrix bitMatrix) throws FormatException { int numRows = bitMatrix.getHeight(); int numColumns = bitMatrix.getWidth(); return Version.getVersionForDimensions(numRows, numColumns); } /** *Reads the bits in the {@link BitMatrix} representing the mapping matrix (No alignment patterns) * in the correct order in order to reconstitute the codewords bytes contained within the * Data Matrix Code.
* * @return bytes encoded within the Data Matrix Code * @throws FormatException if the exact number of bytes expected is not read */ byte[] readCodewords() throws FormatException { byte[] result = new byte[version.getTotalCodewords()]; int resultOffset = 0; int row = 4; int column = 0; int numRows = mappingBitMatrix.getHeight(); int numColumns = mappingBitMatrix.getWidth(); boolean corner1Read = false; boolean corner2Read = false; boolean corner3Read = false; boolean corner4Read = false; // Read all of the codewords do { // Check the four corner cases if ((row == numRows) && (column == 0) && !corner1Read) { result[resultOffset++] = (byte) readCorner1(numRows, numColumns); row -= 2; column += 2; corner1Read = true; } else if ((row == numRows - 2) && (column == 0) && ((numColumns & 0x03) != 0) && !corner2Read) { result[resultOffset++] = (byte) readCorner2(numRows, numColumns); row -= 2; column += 2; corner2Read = true; } else if ((row == numRows + 4) && (column == 2) && ((numColumns & 0x07) == 0) && !corner3Read) { result[resultOffset++] = (byte) readCorner3(numRows, numColumns); row -= 2; column += 2; corner3Read = true; } else if ((row == numRows - 2) && (column == 0) && ((numColumns & 0x07) == 4) && !corner4Read) { result[resultOffset++] = (byte) readCorner4(numRows, numColumns); row -= 2; column += 2; corner4Read = true; } else { // Sweep upward diagonally to the right do { if ((row < numRows) && (column >= 0) && !readMappingMatrix.get(column, row)) { result[resultOffset++] = (byte) readUtah(row, column, numRows, numColumns); } row -= 2; column += 2; } while ((row >= 0) && (column < numColumns)); row += 1; column += 3; // Sweep downward diagonally to the left do { if ((row >= 0) && (column < numColumns) && !readMappingMatrix.get(column, row)) { result[resultOffset++] = (byte) readUtah(row, column, numRows, numColumns); } row += 2; column -= 2; } while ((row < numRows) && (column >= 0)); row += 3; column += 1; } } while ((row < numRows) || (column < numColumns)); if (resultOffset != version.getTotalCodewords()) { throw FormatException.getFormatInstance(); } return result; } /** *Reads a bit of the mapping matrix accounting for boundary wrapping.
* * @param row Row to read in the mapping matrix * @param column Column to read in the mapping matrix * @param numRows Number of rows in the mapping matrix * @param numColumns Number of columns in the mapping matrix * @return value of the given bit in the mapping matrix */ private boolean readModule(int row, int column, int numRows, int numColumns) { // Adjust the row and column indices based on boundary wrapping if (row < 0) { row += numRows; column += 4 - ((numRows + 4) & 0x07); } if (column < 0) { column += numColumns; row += 4 - ((numColumns + 4) & 0x07); } readMappingMatrix.set(column, row); return mappingBitMatrix.get(column, row); } /** *Reads the 8 bits of the standard Utah-shaped pattern.
* *See ISO 16022:2006, 5.8.1 Figure 6
* * @param row Current row in the mapping matrix, anchored at the 8th bit (LSB) of the pattern * @param column Current column in the mapping matrix, anchored at the 8th bit (LSB) of the pattern * @param numRows Number of rows in the mapping matrix * @param numColumns Number of columns in the mapping matrix * @return byte from the utah shape */ private int readUtah(int row, int column, int numRows, int numColumns) { int currentByte = 0; if (readModule(row - 2, column - 2, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(row - 2, column - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(row - 1, column - 2, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(row - 1, column - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(row - 1, column, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(row, column - 2, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(row, column - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(row, column, numRows, numColumns)) { currentByte |= 1; } return currentByte; } /** *Reads the 8 bits of the special corner condition 1.
* *See ISO 16022:2006, Figure F.3
* * @param numRows Number of rows in the mapping matrix * @param numColumns Number of columns in the mapping matrix * @return byte from the Corner condition 1 */ private int readCorner1(int numRows, int numColumns) { int currentByte = 0; if (readModule(numRows - 1, 0, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(numRows - 1, 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(numRows - 1, 2, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(0, numColumns - 2, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(0, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(1, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(2, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(3, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } return currentByte; } /** *Reads the 8 bits of the special corner condition 2.
* *See ISO 16022:2006, Figure F.4
* * @param numRows Number of rows in the mapping matrix * @param numColumns Number of columns in the mapping matrix * @return byte from the Corner condition 2 */ private int readCorner2(int numRows, int numColumns) { int currentByte = 0; if (readModule(numRows - 3, 0, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(numRows - 2, 0, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(numRows - 1, 0, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(0, numColumns - 4, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(0, numColumns - 3, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(0, numColumns - 2, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(0, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(1, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } return currentByte; } /** *Reads the 8 bits of the special corner condition 3.
* *See ISO 16022:2006, Figure F.5
* * @param numRows Number of rows in the mapping matrix * @param numColumns Number of columns in the mapping matrix * @return byte from the Corner condition 3 */ private int readCorner3(int numRows, int numColumns) { int currentByte = 0; if (readModule(numRows - 1, 0, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(numRows - 1, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(0, numColumns - 3, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(0, numColumns - 2, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(0, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(1, numColumns - 3, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(1, numColumns - 2, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(1, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } return currentByte; } /** *Reads the 8 bits of the special corner condition 4.
* *See ISO 16022:2006, Figure F.6
* * @param numRows Number of rows in the mapping matrix * @param numColumns Number of columns in the mapping matrix * @return byte from the Corner condition 4 */ private int readCorner4(int numRows, int numColumns) { int currentByte = 0; if (readModule(numRows - 3, 0, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(numRows - 2, 0, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(numRows - 1, 0, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(0, numColumns - 2, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(0, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(1, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(2, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } currentByte <<= 1; if (readModule(3, numColumns - 1, numRows, numColumns)) { currentByte |= 1; } return currentByte; } /** *Extracts the data region from a {@link BitMatrix} that contains * alignment patterns.
* * @param bitMatrix Original {@link BitMatrix} with alignment patterns * @return BitMatrix that has the alignment patterns removed */ private BitMatrix extractDataRegion(BitMatrix bitMatrix) { int symbolSizeRows = version.getSymbolSizeRows(); int symbolSizeColumns = version.getSymbolSizeColumns(); if (bitMatrix.getHeight() != symbolSizeRows) { throw new IllegalArgumentException("Dimension of bitMatrix must match the version size"); } int dataRegionSizeRows = version.getDataRegionSizeRows(); int dataRegionSizeColumns = version.getDataRegionSizeColumns(); int numDataRegionsRow = symbolSizeRows / dataRegionSizeRows; int numDataRegionsColumn = symbolSizeColumns / dataRegionSizeColumns; int sizeDataRegionRow = numDataRegionsRow * dataRegionSizeRows; int sizeDataRegionColumn = numDataRegionsColumn * dataRegionSizeColumns; BitMatrix bitMatrixWithoutAlignment = new BitMatrix(sizeDataRegionColumn, sizeDataRegionRow); for (int dataRegionRow = 0; dataRegionRow < numDataRegionsRow; ++dataRegionRow) { int dataRegionRowOffset = dataRegionRow * dataRegionSizeRows; for (int dataRegionColumn = 0; dataRegionColumn < numDataRegionsColumn; ++dataRegionColumn) { int dataRegionColumnOffset = dataRegionColumn * dataRegionSizeColumns; for (int i = 0; i < dataRegionSizeRows; ++i) { int readRowOffset = dataRegionRow * (dataRegionSizeRows + 2) + 1 + i; int writeRowOffset = dataRegionRowOffset + i; for (int j = 0; j < dataRegionSizeColumns; ++j) { int readColumnOffset = dataRegionColumn * (dataRegionSizeColumns + 2) + 1 + j; if (bitMatrix.get(readColumnOffset, readRowOffset)) { int writeColumnOffset = dataRegionColumnOffset + j; bitMatrixWithoutAlignment.set(writeColumnOffset, writeRowOffset); } } } } } return bitMatrixWithoutAlignment; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/decoder/DataBlock.java ================================================ /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.datamatrix.decoder; /** *Encapsulates a block of data within a Data Matrix Code. Data Matrix Codes may split their data into * multiple blocks, each of which is a unit of data and error-correction codewords. Each * is represented by an instance of this class.
* * @author bbrown@google.com (Brian Brown) */ final class DataBlock { private final int numDataCodewords; private final byte[] codewords; private DataBlock(int numDataCodewords, byte[] codewords) { this.numDataCodewords = numDataCodewords; this.codewords = codewords; } /** *When Data Matrix Codes use multiple data blocks, they actually interleave the bytes of each of them. * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This * method will separate the data into original blocks.
* * @param rawCodewords bytes as read directly from the Data Matrix Code * @param version version of the Data Matrix Code * @return DataBlocks containing original bytes, "de-interleaved" from representation in the * Data Matrix Code */ static DataBlock[] getDataBlocks(byte[] rawCodewords, Version version) { // Figure out the number and size of data blocks used by this version Version.ECBlocks ecBlocks = version.getECBlocks(); // First count the total number of data blocks int totalBlocks = 0; Version.ECB[] ecBlockArray = ecBlocks.getECBlocks(); for (Version.ECB ecBlock : ecBlockArray) { totalBlocks += ecBlock.getCount(); } // Now establish DataBlocks of the appropriate size and number of data codewords DataBlock[] result = new DataBlock[totalBlocks]; int numResultBlocks = 0; for (Version.ECB ecBlock : ecBlockArray) { for (int i = 0; i < ecBlock.getCount(); i++) { int numDataCodewords = ecBlock.getDataCodewords(); int numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords; result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]); } } // All blocks have the same amount of data, except that the last n // (where n may be 0) have 1 less byte. Figure out where these start. // TODO(bbrown): There is only one case where there is a difference for Data Matrix for size 144 int longerBlocksTotalCodewords = result[0].codewords.length; //int shorterBlocksTotalCodewords = longerBlocksTotalCodewords - 1; int longerBlocksNumDataCodewords = longerBlocksTotalCodewords - ecBlocks.getECCodewords(); int shorterBlocksNumDataCodewords = longerBlocksNumDataCodewords - 1; // The last elements of result may be 1 element shorter for 144 matrix // first fill out as many elements as all of them have minus 1 int rawCodewordsOffset = 0; for (int i = 0; i < shorterBlocksNumDataCodewords; i++) { for (int j = 0; j < numResultBlocks; j++) { result[j].codewords[i] = rawCodewords[rawCodewordsOffset++]; } } // Fill out the last data block in the longer ones boolean specialVersion = version.getVersionNumber() == 24; int numLongerBlocks = specialVersion ? 8 : numResultBlocks; for (int j = 0; j < numLongerBlocks; j++) { result[j].codewords[longerBlocksNumDataCodewords - 1] = rawCodewords[rawCodewordsOffset++]; } // Now add in error correction blocks int max = result[0].codewords.length; for (int i = longerBlocksNumDataCodewords; i < max; i++) { for (int j = 0; j < numResultBlocks; j++) { int jOffset = specialVersion ? (j + 8) % numResultBlocks : j; int iOffset = specialVersion && jOffset > 7 ? i - 1 : i; result[jOffset].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; } } if (rawCodewordsOffset != rawCodewords.length) { throw new IllegalArgumentException(); } return result; } int getNumDataCodewords() { return numDataCodewords; } byte[] getCodewords() { return codewords; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/decoder/DecodedBitStreamParser.java ================================================ /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.datamatrix.decoder; import com.google.zxing.FormatException; import com.google.zxing.common.BitSource; import com.google.zxing.common.DecoderResult; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** *Data Matrix Codes can encode text as bits in one of several modes, and can use multiple modes * in one Data Matrix Code. This class decodes the bits back into text.
* *See ISO 16022:2006, 5.2.1 - 5.2.9.2
* * @author bbrown@google.com (Brian Brown) * @author Sean Owen */ final class DecodedBitStreamParser { private enum Mode { PAD_ENCODE, // Not really a mode ASCII_ENCODE, C40_ENCODE, TEXT_ENCODE, ANSIX12_ENCODE, EDIFACT_ENCODE, BASE256_ENCODE } /** * See ISO 16022:2006, Annex C Table C.1 * The C40 Basic Character Set (*'s used for placeholders for the shift values) */ private static final char[] C40_BASIC_SET_CHARS = { '*', '*', '*', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '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' }; private static final char[] C40_SHIFT2_SET_CHARS = { '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_' }; /** * See ISO 16022:2006, Annex C Table C.2 * The Text Basic Character Set (*'s used for placeholders for the shift values) */ private static final char[] TEXT_BASIC_SET_CHARS = { '*', '*', '*', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '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' }; // Shift 2 for Text is the same encoding as C40 private static final char[] TEXT_SHIFT2_SET_CHARS = C40_SHIFT2_SET_CHARS; private static final char[] TEXT_SHIFT3_SET_CHARS = { '`', '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', '{', '|', '}', '~', (char) 127 }; private DecodedBitStreamParser() { } static DecoderResult decode(byte[] bytes) throws FormatException { BitSource bits = new BitSource(bytes); StringBuilder result = new StringBuilder(100); StringBuilder resultTrailer = new StringBuilder(0); ListThe main class which implements Data Matrix Code decoding -- as opposed to locating and extracting * the Data Matrix Code from an image.
* * @author bbrown@google.com (Brian Brown) */ public final class Decoder { private final ReedSolomonDecoder rsDecoder; public Decoder() { rsDecoder = new ReedSolomonDecoder(GenericGF.DATA_MATRIX_FIELD_256); } /** *Convenience method that can decode a Data Matrix Code represented as a 2D array of booleans. * "true" is taken to mean a black module.
* * @param image booleans representing white/black Data Matrix Code modules * @return text and bytes encoded within the Data Matrix Code * @throws FormatException if the Data Matrix Code cannot be decoded * @throws ChecksumException if error correction fails */ public DecoderResult decode(boolean[][] image) throws FormatException, ChecksumException { return decode(BitMatrix.parse(image)); } /** *Decodes a Data Matrix Code represented as a {@link BitMatrix}. A 1 or "true" is taken * to mean a black module.
* * @param bits booleans representing white/black Data Matrix Code modules * @return text and bytes encoded within the Data Matrix Code * @throws FormatException if the Data Matrix Code cannot be decoded * @throws ChecksumException if error correction fails */ public DecoderResult decode(BitMatrix bits) throws FormatException, ChecksumException { // Construct a parser and read version, error-correction level BitMatrixParser parser = new BitMatrixParser(bits); Version version = parser.getVersion(); // Read codewords byte[] codewords = parser.readCodewords(); // Separate into data blocks DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version); // Count total number of data bytes int totalBytes = 0; for (DataBlock db : dataBlocks) { totalBytes += db.getNumDataCodewords(); } byte[] resultBytes = new byte[totalBytes]; int dataBlocksCount = dataBlocks.length; // Error-correct and copy data blocks together into a stream of bytes for (int j = 0; j < dataBlocksCount; j++) { DataBlock dataBlock = dataBlocks[j]; byte[] codewordBytes = dataBlock.getCodewords(); int numDataCodewords = dataBlock.getNumDataCodewords(); correctErrors(codewordBytes, numDataCodewords); for (int i = 0; i < numDataCodewords; i++) { // De-interlace data blocks. resultBytes[i * dataBlocksCount + j] = codewordBytes[i]; } } // Decode the contents of that stream of bytes return DecodedBitStreamParser.decode(resultBytes); } /** *Given data and error-correction codewords received, possibly corrupted by errors, attempts to * correct the errors in-place using Reed-Solomon error correction.
* * @param codewordBytes data and error correction codewords * @param numDataCodewords number of codewords that are data bytes * @throws ChecksumException if error correction fails */ private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException { int numCodewords = codewordBytes.length; // First read into an array of ints int[] codewordsInts = new int[numCodewords]; for (int i = 0; i < numCodewords; i++) { codewordsInts[i] = codewordBytes[i] & 0xFF; } try { rsDecoder.decode(codewordsInts, codewordBytes.length - numDataCodewords); } catch (ReedSolomonException ignored) { throw ChecksumException.getChecksumInstance(); } // Copy back into array of bytes -- only need to worry about the bytes that were data // We don't care about errors in the error-correction codewords for (int i = 0; i < numDataCodewords; i++) { codewordBytes[i] = (byte) codewordsInts[i]; } } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/decoder/Version.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.datamatrix.decoder; import com.google.zxing.FormatException; /** * The Version object encapsulates attributes about a particular * size Data Matrix Code. * * @author bbrown@google.com (Brian Brown) */ public final class Version { private static final Version[] VERSIONS = buildVersions(); private final int versionNumber; private final int symbolSizeRows; private final int symbolSizeColumns; private final int dataRegionSizeRows; private final int dataRegionSizeColumns; private final ECBlocks ecBlocks; private final int totalCodewords; private Version(int versionNumber, int symbolSizeRows, int symbolSizeColumns, int dataRegionSizeRows, int dataRegionSizeColumns, ECBlocks ecBlocks) { this.versionNumber = versionNumber; this.symbolSizeRows = symbolSizeRows; this.symbolSizeColumns = symbolSizeColumns; this.dataRegionSizeRows = dataRegionSizeRows; this.dataRegionSizeColumns = dataRegionSizeColumns; this.ecBlocks = ecBlocks; // Calculate the total number of codewords int total = 0; int ecCodewords = ecBlocks.getECCodewords(); ECB[] ecbArray = ecBlocks.getECBlocks(); for (ECB ecBlock : ecbArray) { total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords); } this.totalCodewords = total; } public int getVersionNumber() { return versionNumber; } public int getSymbolSizeRows() { return symbolSizeRows; } public int getSymbolSizeColumns() { return symbolSizeColumns; } public int getDataRegionSizeRows() { return dataRegionSizeRows; } public int getDataRegionSizeColumns() { return dataRegionSizeColumns; } public int getTotalCodewords() { return totalCodewords; } ECBlocks getECBlocks() { return ecBlocks; } /** *Deduces version information from Data Matrix dimensions.
* * @param numRows Number of rows in modules * @param numColumns Number of columns in modules * @return Version for a Data Matrix Code of those dimensions * @throws FormatException if dimensions do correspond to a valid Data Matrix size */ public static Version getVersionForDimensions(int numRows, int numColumns) throws FormatException { if ((numRows & 0x01) != 0 || (numColumns & 0x01) != 0) { throw FormatException.getFormatInstance(); } for (Version version : VERSIONS) { if (version.symbolSizeRows == numRows && version.symbolSizeColumns == numColumns) { return version; } } throw FormatException.getFormatInstance(); } /** *Encapsulates a set of error-correction blocks in one symbol version. Most versions will * use blocks of differing sizes within one version, so, this encapsulates the parameters for * each set of blocks. It also holds the number of error-correction codewords per block since it * will be the same across all blocks within one version.
*/ static final class ECBlocks { private final int ecCodewords; private final ECB[] ecBlocks; private ECBlocks(int ecCodewords, ECB ecBlocks) { this.ecCodewords = ecCodewords; this.ecBlocks = new ECB[] { ecBlocks }; } private ECBlocks(int ecCodewords, ECB ecBlocks1, ECB ecBlocks2) { this.ecCodewords = ecCodewords; this.ecBlocks = new ECB[] { ecBlocks1, ecBlocks2 }; } int getECCodewords() { return ecCodewords; } ECB[] getECBlocks() { return ecBlocks; } } /** *Encapsulates the parameters for one error-correction block in one symbol version. * This includes the number of data codewords, and the number of times a block with these * parameters is used consecutively in the Data Matrix code version's format.
*/ static final class ECB { private final int count; private final int dataCodewords; private ECB(int count, int dataCodewords) { this.count = count; this.dataCodewords = dataCodewords; } int getCount() { return count; } int getDataCodewords() { return dataCodewords; } } @Override public String toString() { return String.valueOf(versionNumber); } /** * See ISO 16022:2006 5.5.1 Table 7 */ private static Version[] buildVersions() { return new Version[]{ new Version(1, 10, 10, 8, 8, new ECBlocks(5, new ECB(1, 3))), new Version(2, 12, 12, 10, 10, new ECBlocks(7, new ECB(1, 5))), new Version(3, 14, 14, 12, 12, new ECBlocks(10, new ECB(1, 8))), new Version(4, 16, 16, 14, 14, new ECBlocks(12, new ECB(1, 12))), new Version(5, 18, 18, 16, 16, new ECBlocks(14, new ECB(1, 18))), new Version(6, 20, 20, 18, 18, new ECBlocks(18, new ECB(1, 22))), new Version(7, 22, 22, 20, 20, new ECBlocks(20, new ECB(1, 30))), new Version(8, 24, 24, 22, 22, new ECBlocks(24, new ECB(1, 36))), new Version(9, 26, 26, 24, 24, new ECBlocks(28, new ECB(1, 44))), new Version(10, 32, 32, 14, 14, new ECBlocks(36, new ECB(1, 62))), new Version(11, 36, 36, 16, 16, new ECBlocks(42, new ECB(1, 86))), new Version(12, 40, 40, 18, 18, new ECBlocks(48, new ECB(1, 114))), new Version(13, 44, 44, 20, 20, new ECBlocks(56, new ECB(1, 144))), new Version(14, 48, 48, 22, 22, new ECBlocks(68, new ECB(1, 174))), new Version(15, 52, 52, 24, 24, new ECBlocks(42, new ECB(2, 102))), new Version(16, 64, 64, 14, 14, new ECBlocks(56, new ECB(2, 140))), new Version(17, 72, 72, 16, 16, new ECBlocks(36, new ECB(4, 92))), new Version(18, 80, 80, 18, 18, new ECBlocks(48, new ECB(4, 114))), new Version(19, 88, 88, 20, 20, new ECBlocks(56, new ECB(4, 144))), new Version(20, 96, 96, 22, 22, new ECBlocks(68, new ECB(4, 174))), new Version(21, 104, 104, 24, 24, new ECBlocks(56, new ECB(6, 136))), new Version(22, 120, 120, 18, 18, new ECBlocks(68, new ECB(6, 175))), new Version(23, 132, 132, 20, 20, new ECBlocks(62, new ECB(8, 163))), new Version(24, 144, 144, 22, 22, new ECBlocks(62, new ECB(8, 156), new ECB(2, 155))), new Version(25, 8, 18, 6, 16, new ECBlocks(7, new ECB(1, 5))), new Version(26, 8, 32, 6, 14, new ECBlocks(11, new ECB(1, 10))), new Version(27, 12, 26, 10, 24, new ECBlocks(14, new ECB(1, 16))), new Version(28, 12, 36, 10, 16, new ECBlocks(18, new ECB(1, 22))), new Version(29, 16, 36, 14, 16, new ECBlocks(24, new ECB(1, 32))), new Version(30, 16, 48, 14, 22, new ECBlocks(28, new ECB(1, 49))) }; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/detector/Detector.java ================================================ /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.datamatrix.detector; import com.google.zxing.NotFoundException; import com.google.zxing.ResultPoint; import com.google.zxing.common.BitMatrix; import com.google.zxing.common.DetectorResult; import com.google.zxing.common.GridSampler; import com.google.zxing.common.detector.WhiteRectangleDetector; /** *Encapsulates logic that can detect a Data Matrix Code in an image, even if the Data Matrix Code * is rotated or skewed, or partially obscured.
* * @author Sean Owen */ public final class Detector { private final BitMatrix image; private final WhiteRectangleDetector rectangleDetector; public Detector(BitMatrix image) throws NotFoundException { this.image = image; rectangleDetector = new WhiteRectangleDetector(image); } /** *Detects a Data Matrix Code in an image.
* * @return {@link DetectorResult} encapsulating results of detecting a Data Matrix Code * @throws NotFoundException if no Data Matrix Code can be found */ public DetectorResult detect() throws NotFoundException { ResultPoint[] cornerPoints = rectangleDetector.detect(); ResultPoint[] points = detectSolid1(cornerPoints); points = detectSolid2(points); points[3] = correctTopRight(points); if (points[3] == null) { throw NotFoundException.getNotFoundInstance(); } points = shiftToModuleCenter(points); ResultPoint topLeft = points[0]; ResultPoint bottomLeft = points[1]; ResultPoint bottomRight = points[2]; ResultPoint topRight = points[3]; int dimensionTop = transitionsBetween(topLeft, topRight) + 1; int dimensionRight = transitionsBetween(bottomRight, topRight) + 1; if ((dimensionTop & 0x01) == 1) { dimensionTop += 1; } if ((dimensionRight & 0x01) == 1) { dimensionRight += 1; } if (4 * dimensionTop < 7 * dimensionRight && 4 * dimensionRight < 7 * dimensionTop) { // The matrix is square dimensionTop = dimensionRight = Math.max(dimensionTop, dimensionRight); } BitMatrix bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, topRight, dimensionTop, dimensionRight); return new DetectorResult(bits, new ResultPoint[]{topLeft, bottomLeft, bottomRight, topRight}); } private ResultPoint shiftPoint(ResultPoint point, ResultPoint to, int div) { float x = (to.getX() - point.getX()) / (div + 1); float y = (to.getY() - point.getY()) / (div + 1); return new ResultPoint(point.getX() + x, point.getY() + y); } private ResultPoint moveAway(ResultPoint point, float fromX, float fromY) { float x = point.getX(); float y = point.getY(); if (x < fromX) { x -= 1; } else { x += 1; } if (y < fromY) { y -= 1; } else { y += 1; } return new ResultPoint(x, y); } /** * Detect a solid side which has minimum transition. */ private ResultPoint[] detectSolid1(ResultPoint[] cornerPoints) { // 0 2 // 1 3 ResultPoint pointA = cornerPoints[0]; ResultPoint pointB = cornerPoints[1]; ResultPoint pointC = cornerPoints[3]; ResultPoint pointD = cornerPoints[2]; int trAB = transitionsBetween(pointA, pointB); int trBC = transitionsBetween(pointB, pointC); int trCD = transitionsBetween(pointC, pointD); int trDA = transitionsBetween(pointD, pointA); // 0..3 // : : // 1--2 int min = trAB; ResultPoint[] points = {pointD, pointA, pointB, pointC}; if (min > trBC) { min = trBC; points[0] = pointA; points[1] = pointB; points[2] = pointC; points[3] = pointD; } if (min > trCD) { min = trCD; points[0] = pointB; points[1] = pointC; points[2] = pointD; points[3] = pointA; } if (min > trDA) { points[0] = pointC; points[1] = pointD; points[2] = pointA; points[3] = pointB; } return points; } /** * Detect a second solid side next to first solid side. */ private ResultPoint[] detectSolid2(ResultPoint[] points) { // A..D // : : // B--C ResultPoint pointA = points[0]; ResultPoint pointB = points[1]; ResultPoint pointC = points[2]; ResultPoint pointD = points[3]; // Transition detection on the edge is not stable. // To safely detect, shift the points to the module center. int tr = transitionsBetween(pointA, pointD); ResultPoint pointBs = shiftPoint(pointB, pointC, (tr + 1) * 4); ResultPoint pointCs = shiftPoint(pointC, pointB, (tr + 1) * 4); int trBA = transitionsBetween(pointBs, pointA); int trCD = transitionsBetween(pointCs, pointD); // 0..3 // | : // 1--2 if (trBA < trCD) { // solid sides: A-B-C points[0] = pointA; points[1] = pointB; points[2] = pointC; points[3] = pointD; } else { // solid sides: B-C-D points[0] = pointB; points[1] = pointC; points[2] = pointD; points[3] = pointA; } return points; } /** * Calculates the corner position of the white top right module. */ private ResultPoint correctTopRight(ResultPoint[] points) { // A..D // | : // B--C ResultPoint pointA = points[0]; ResultPoint pointB = points[1]; ResultPoint pointC = points[2]; ResultPoint pointD = points[3]; // shift points for safe transition detection. int trTop = transitionsBetween(pointA, pointD); int trRight = transitionsBetween(pointB, pointD); ResultPoint pointAs = shiftPoint(pointA, pointB, (trRight + 1) * 4); ResultPoint pointCs = shiftPoint(pointC, pointB, (trTop + 1) * 4); trTop = transitionsBetween(pointAs, pointD); trRight = transitionsBetween(pointCs, pointD); ResultPoint candidate1 = new ResultPoint( pointD.getX() + (pointC.getX() - pointB.getX()) / (trTop + 1), pointD.getY() + (pointC.getY() - pointB.getY()) / (trTop + 1)); ResultPoint candidate2 = new ResultPoint( pointD.getX() + (pointA.getX() - pointB.getX()) / (trRight + 1), pointD.getY() + (pointA.getY() - pointB.getY()) / (trRight + 1)); if (!isValid(candidate1)) { if (isValid(candidate2)) { return candidate2; } return null; } if (!isValid(candidate2)) { return candidate1; } int sumc1 = transitionsBetween(pointAs, candidate1) + transitionsBetween(pointCs, candidate1); int sumc2 = transitionsBetween(pointAs, candidate2) + transitionsBetween(pointCs, candidate2); if (sumc1 > sumc2) { return candidate1; } else { return candidate2; } } /** * Shift the edge points to the module center. */ private ResultPoint[] shiftToModuleCenter(ResultPoint[] points) { // A..D // | : // B--C ResultPoint pointA = points[0]; ResultPoint pointB = points[1]; ResultPoint pointC = points[2]; ResultPoint pointD = points[3]; // calculate pseudo dimensions int dimH = transitionsBetween(pointA, pointD) + 1; int dimV = transitionsBetween(pointC, pointD) + 1; // shift points for safe dimension detection ResultPoint pointAs = shiftPoint(pointA, pointB, dimV * 4); ResultPoint pointCs = shiftPoint(pointC, pointB, dimH * 4); // calculate more precise dimensions dimH = transitionsBetween(pointAs, pointD) + 1; dimV = transitionsBetween(pointCs, pointD) + 1; if ((dimH & 0x01) == 1) { dimH += 1; } if ((dimV & 0x01) == 1) { dimV += 1; } // WhiteRectangleDetector returns points inside of the rectangle. // I want points on the edges. float centerX = (pointA.getX() + pointB.getX() + pointC.getX() + pointD.getX()) / 4; float centerY = (pointA.getY() + pointB.getY() + pointC.getY() + pointD.getY()) / 4; pointA = moveAway(pointA, centerX, centerY); pointB = moveAway(pointB, centerX, centerY); pointC = moveAway(pointC, centerX, centerY); pointD = moveAway(pointD, centerX, centerY); ResultPoint pointBs; ResultPoint pointDs; // shift points to the center of each modules pointAs = shiftPoint(pointA, pointB, dimV * 4); pointAs = shiftPoint(pointAs, pointD, dimH * 4); pointBs = shiftPoint(pointB, pointA, dimV * 4); pointBs = shiftPoint(pointBs, pointC, dimH * 4); pointCs = shiftPoint(pointC, pointD, dimV * 4); pointCs = shiftPoint(pointCs, pointB, dimH * 4); pointDs = shiftPoint(pointD, pointC, dimV * 4); pointDs = shiftPoint(pointDs, pointA, dimH * 4); return new ResultPoint[]{pointAs, pointBs, pointCs, pointDs}; } private boolean isValid(ResultPoint p) { return p.getX() >= 0 && p.getX() < image.getWidth() && p.getY() > 0 && p.getY() < image.getHeight(); } private static BitMatrix sampleGrid(BitMatrix image, ResultPoint topLeft, ResultPoint bottomLeft, ResultPoint bottomRight, ResultPoint topRight, int dimensionX, int dimensionY) throws NotFoundException { GridSampler sampler = GridSampler.getInstance(); return sampler.sampleGrid(image, dimensionX, dimensionY, 0.5f, 0.5f, dimensionX - 0.5f, 0.5f, dimensionX - 0.5f, dimensionY - 0.5f, 0.5f, dimensionY - 0.5f, topLeft.getX(), topLeft.getY(), topRight.getX(), topRight.getY(), bottomRight.getX(), bottomRight.getY(), bottomLeft.getX(), bottomLeft.getY()); } /** * Counts the number of black/white transitions between two points, using something like Bresenham's algorithm. */ private int transitionsBetween(ResultPoint from, ResultPoint to) { // See QR Code Detector, sizeOfBlackWhiteBlackRun() int fromX = (int) from.getX(); int fromY = (int) from.getY(); int toX = (int) to.getX(); int toY = (int) to.getY(); boolean steep = Math.abs(toY - fromY) > Math.abs(toX - fromX); if (steep) { int temp = fromX; fromX = fromY; fromY = temp; temp = toX; toX = toY; toY = temp; } int dx = Math.abs(toX - fromX); int dy = Math.abs(toY - fromY); int error = -dx / 2; int ystep = fromY < toY ? 1 : -1; int xstep = fromX < toX ? 1 : -1; int transitions = 0; boolean inBlack = image.get(steep ? fromY : fromX, steep ? fromX : fromY); for (int x = fromX, y = fromY; x != toX; x += xstep) { boolean isBlack = image.get(steep ? y : x, steep ? x : y); if (isBlack != inBlack) { transitions++; inBlack = isBlack; } error += dy; if (error > 0) { if (y == toY) { break; } y += ystep; error -= dx; } } return transitions; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/ASCIIEncoder.java ================================================ /* * Copyright 2006-2007 Jeremias Maerki. * * 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.datamatrix.encoder; final class ASCIIEncoder implements Encoder { @Override public int getEncodingMode() { return HighLevelEncoder.ASCII_ENCODATION; } @Override public void encode(EncoderContext context) { //step B int n = HighLevelEncoder.determineConsecutiveDigitCount(context.getMessage(), context.pos); if (n >= 2) { context.writeCodeword(encodeASCIIDigits(context.getMessage().charAt(context.pos), context.getMessage().charAt(context.pos + 1))); context.pos += 2; } else { char c = context.getCurrentChar(); int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode()); if (newMode != getEncodingMode()) { switch (newMode) { case HighLevelEncoder.BASE256_ENCODATION: context.writeCodeword(HighLevelEncoder.LATCH_TO_BASE256); context.signalEncoderChange(HighLevelEncoder.BASE256_ENCODATION); return; case HighLevelEncoder.C40_ENCODATION: context.writeCodeword(HighLevelEncoder.LATCH_TO_C40); context.signalEncoderChange(HighLevelEncoder.C40_ENCODATION); return; case HighLevelEncoder.X12_ENCODATION: context.writeCodeword(HighLevelEncoder.LATCH_TO_ANSIX12); context.signalEncoderChange(HighLevelEncoder.X12_ENCODATION); break; case HighLevelEncoder.TEXT_ENCODATION: context.writeCodeword(HighLevelEncoder.LATCH_TO_TEXT); context.signalEncoderChange(HighLevelEncoder.TEXT_ENCODATION); break; case HighLevelEncoder.EDIFACT_ENCODATION: context.writeCodeword(HighLevelEncoder.LATCH_TO_EDIFACT); context.signalEncoderChange(HighLevelEncoder.EDIFACT_ENCODATION); break; default: throw new IllegalStateException("Illegal mode: " + newMode); } } else if (HighLevelEncoder.isExtendedASCII(c)) { context.writeCodeword(HighLevelEncoder.UPPER_SHIFT); context.writeCodeword((char) (c - 128 + 1)); context.pos++; } else { context.writeCodeword((char) (c + 1)); context.pos++; } } } private static char encodeASCIIDigits(char digit1, char digit2) { if (HighLevelEncoder.isDigit(digit1) && HighLevelEncoder.isDigit(digit2)) { int num = (digit1 - 48) * 10 + (digit2 - 48); return (char) (num + 130); } throw new IllegalArgumentException("not digits: " + digit1 + digit2); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/Base256Encoder.java ================================================ /* * Copyright 2006-2007 Jeremias Maerki. * * 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.datamatrix.encoder; final class Base256Encoder implements Encoder { @Override public int getEncodingMode() { return HighLevelEncoder.BASE256_ENCODATION; } @Override public void encode(EncoderContext context) { StringBuilder buffer = new StringBuilder(); buffer.append('\0'); //Initialize length field while (context.hasMoreCharacters()) { char c = context.getCurrentChar(); buffer.append(c); context.pos++; int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode()); if (newMode != getEncodingMode()) { // Return to ASCII encodation, which will actually handle latch to new mode context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION); break; } } int dataCount = buffer.length() - 1; int lengthFieldSize = 1; int currentSize = context.getCodewordCount() + dataCount + lengthFieldSize; context.updateSymbolInfo(currentSize); boolean mustPad = (context.getSymbolInfo().getDataCapacity() - currentSize) > 0; if (context.hasMoreCharacters() || mustPad) { if (dataCount <= 249) { buffer.setCharAt(0, (char) dataCount); } else if (dataCount <= 1555) { buffer.setCharAt(0, (char) ((dataCount / 250) + 249)); buffer.insert(1, (char) (dataCount % 250)); } else { throw new IllegalStateException( "Message length not in valid ranges: " + dataCount); } } for (int i = 0, c = buffer.length(); i < c; i++) { context.writeCodeword(randomize255State( buffer.charAt(i), context.getCodewordCount() + 1)); } } private static char randomize255State(char ch, int codewordPosition) { int pseudoRandom = ((149 * codewordPosition) % 255) + 1; int tempVariable = ch + pseudoRandom; if (tempVariable <= 255) { return (char) tempVariable; } else { return (char) (tempVariable - 256); } } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/C40Encoder.java ================================================ /* * Copyright 2006-2007 Jeremias Maerki. * * 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.datamatrix.encoder; class C40Encoder implements Encoder { @Override public int getEncodingMode() { return HighLevelEncoder.C40_ENCODATION; } @Override public void encode(EncoderContext context) { //step C StringBuilder buffer = new StringBuilder(); while (context.hasMoreCharacters()) { char c = context.getCurrentChar(); context.pos++; int lastCharSize = encodeChar(c, buffer); int unwritten = (buffer.length() / 3) * 2; int curCodewordCount = context.getCodewordCount() + unwritten; context.updateSymbolInfo(curCodewordCount); int available = context.getSymbolInfo().getDataCapacity() - curCodewordCount; if (!context.hasMoreCharacters()) { //Avoid having a single C40 value in the last triplet StringBuilder removed = new StringBuilder(); if ((buffer.length() % 3) == 2 && (available < 2 || available > 2)) { lastCharSize = backtrackOneCharacter(context, buffer, removed, lastCharSize); } while ((buffer.length() % 3) == 1 && (lastCharSize > 3 || available != 1)) { lastCharSize = backtrackOneCharacter(context, buffer, removed, lastCharSize); } break; } int count = buffer.length(); if ((count % 3) == 0) { int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode()); if (newMode != getEncodingMode()) { // Return to ASCII encodation, which will actually handle latch to new mode context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION); break; } } } handleEOD(context, buffer); } private int backtrackOneCharacter(EncoderContext context, StringBuilder buffer, StringBuilder removed, int lastCharSize) { int count = buffer.length(); buffer.delete(count - lastCharSize, count); context.pos--; char c = context.getCurrentChar(); lastCharSize = encodeChar(c, removed); context.resetSymbolInfo(); //Deal with possible reduction in symbol size return lastCharSize; } static void writeNextTriplet(EncoderContext context, StringBuilder buffer) { context.writeCodewords(encodeToCodewords(buffer, 0)); buffer.delete(0, 3); } /** * Handle "end of data" situations * * @param context the encoder context * @param buffer the buffer with the remaining encoded characters */ void handleEOD(EncoderContext context, StringBuilder buffer) { int unwritten = (buffer.length() / 3) * 2; int rest = buffer.length() % 3; int curCodewordCount = context.getCodewordCount() + unwritten; context.updateSymbolInfo(curCodewordCount); int available = context.getSymbolInfo().getDataCapacity() - curCodewordCount; if (rest == 2) { buffer.append('\0'); //Shift 1 while (buffer.length() >= 3) { writeNextTriplet(context, buffer); } if (context.hasMoreCharacters()) { context.writeCodeword(HighLevelEncoder.C40_UNLATCH); } } else if (available == 1 && rest == 1) { while (buffer.length() >= 3) { writeNextTriplet(context, buffer); } if (context.hasMoreCharacters()) { context.writeCodeword(HighLevelEncoder.C40_UNLATCH); } // else no unlatch context.pos--; } else if (rest == 0) { while (buffer.length() >= 3) { writeNextTriplet(context, buffer); } if (available > 0 || context.hasMoreCharacters()) { context.writeCodeword(HighLevelEncoder.C40_UNLATCH); } } else { throw new IllegalStateException("Unexpected case. Please report!"); } context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION); } int encodeChar(char c, StringBuilder sb) { if (c == ' ') { sb.append('\3'); return 1; } if (c >= '0' && c <= '9') { sb.append((char) (c - 48 + 4)); return 1; } if (c >= 'A' && c <= 'Z') { sb.append((char) (c - 65 + 14)); return 1; } if (c < ' ') { sb.append('\0'); //Shift 1 Set sb.append(c); return 2; } if (c >= '!' && c <= '/') { sb.append('\1'); //Shift 2 Set sb.append((char) (c - 33)); return 2; } if (c >= ':' && c <= '@') { sb.append('\1'); //Shift 2 Set sb.append((char) (c - 58 + 15)); return 2; } if (c >= '[' && c <= '_') { sb.append('\1'); //Shift 2 Set sb.append((char) (c - 91 + 22)); return 2; } if (c >= '`' && c <= 127) { sb.append('\2'); //Shift 3 Set sb.append((char) (c - 96)); return 2; } sb.append("\1\u001e"); //Shift 2, Upper Shift int len = 2; len += encodeChar((char) (c - 128), sb); return len; } private static String encodeToCodewords(CharSequence sb, int startPos) { char c1 = sb.charAt(startPos); char c2 = sb.charAt(startPos + 1); char c3 = sb.charAt(startPos + 2); int v = (1600 * c1) + (40 * c2) + c3 + 1; char cw1 = (char) (v / 256); char cw2 = (char) (v % 256); return new String(new char[] {cw1, cw2}); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/DataMatrixSymbolInfo144.java ================================================ /* * Copyright 2006 Jeremias Maerki * * 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.datamatrix.encoder; final class DataMatrixSymbolInfo144 extends SymbolInfo { DataMatrixSymbolInfo144() { super(false, 1558, 620, 22, 22, 36, -1, 62); } @Override public int getInterleavedBlockCount() { return 10; } @Override public int getDataLengthForInterleavedBlock(int index) { return (index <= 8) ? 156 : 155; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/DefaultPlacement.java ================================================ /* * Copyright 2006 Jeremias Maerki. * * 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.datamatrix.encoder; import java.util.Arrays; /** * Symbol Character Placement Program. Adapted from Annex M.1 in ISO/IEC 16022:2000(E). */ public class DefaultPlacement { private final CharSequence codewords; private final int numrows; private final int numcols; private final byte[] bits; /** * Main constructor * * @param codewords the codewords to place * @param numcols the number of columns * @param numrows the number of rows */ public DefaultPlacement(CharSequence codewords, int numcols, int numrows) { this.codewords = codewords; this.numcols = numcols; this.numrows = numrows; this.bits = new byte[numcols * numrows]; Arrays.fill(this.bits, (byte) -1); //Initialize with "not set" value } final int getNumrows() { return numrows; } final int getNumcols() { return numcols; } final byte[] getBits() { return bits; } public final boolean getBit(int col, int row) { return bits[row * numcols + col] == 1; } private void setBit(int col, int row, boolean bit) { bits[row * numcols + col] = (byte) (bit ? 1 : 0); } private boolean hasBit(int col, int row) { return bits[row * numcols + col] >= 0; } public final void place() { int pos = 0; int row = 4; int col = 0; do { /* repeatedly first check for one of the special corner cases, then... */ if ((row == numrows) && (col == 0)) { corner1(pos++); } if ((row == numrows - 2) && (col == 0) && ((numcols % 4) != 0)) { corner2(pos++); } if ((row == numrows - 2) && (col == 0) && (numcols % 8 == 4)) { corner3(pos++); } if ((row == numrows + 4) && (col == 2) && ((numcols % 8) == 0)) { corner4(pos++); } /* sweep upward diagonally, inserting successive characters... */ do { if ((row < numrows) && (col >= 0) && !hasBit(col, row)) { utah(row, col, pos++); } row -= 2; col += 2; } while (row >= 0 && (col < numcols)); row++; col += 3; /* and then sweep downward diagonally, inserting successive characters, ... */ do { if ((row >= 0) && (col < numcols) && !hasBit(col, row)) { utah(row, col, pos++); } row += 2; col -= 2; } while ((row < numrows) && (col >= 0)); row += 3; col++; /* ...until the entire array is scanned */ } while ((row < numrows) || (col < numcols)); /* Lastly, if the lower righthand corner is untouched, fill in fixed pattern */ if (!hasBit(numcols - 1, numrows - 1)) { setBit(numcols - 1, numrows - 1, true); setBit(numcols - 2, numrows - 2, true); } } private void module(int row, int col, int pos, int bit) { if (row < 0) { row += numrows; col += 4 - ((numrows + 4) % 8); } if (col < 0) { col += numcols; row += 4 - ((numcols + 4) % 8); } // Note the conversion: int v = codewords.charAt(pos); v &= 1 << (8 - bit); setBit(col, row, v != 0); } /** * Places the 8 bits of a utah-shaped symbol character in ECC200. * * @param row the row * @param col the column * @param pos character position */ private void utah(int row, int col, int pos) { module(row - 2, col - 2, pos, 1); module(row - 2, col - 1, pos, 2); module(row - 1, col - 2, pos, 3); module(row - 1, col - 1, pos, 4); module(row - 1, col, pos, 5); module(row, col - 2, pos, 6); module(row, col - 1, pos, 7); module(row, col, pos, 8); } private void corner1(int pos) { module(numrows - 1, 0, pos, 1); module(numrows - 1, 1, pos, 2); module(numrows - 1, 2, pos, 3); module(0, numcols - 2, pos, 4); module(0, numcols - 1, pos, 5); module(1, numcols - 1, pos, 6); module(2, numcols - 1, pos, 7); module(3, numcols - 1, pos, 8); } private void corner2(int pos) { module(numrows - 3, 0, pos, 1); module(numrows - 2, 0, pos, 2); module(numrows - 1, 0, pos, 3); module(0, numcols - 4, pos, 4); module(0, numcols - 3, pos, 5); module(0, numcols - 2, pos, 6); module(0, numcols - 1, pos, 7); module(1, numcols - 1, pos, 8); } private void corner3(int pos) { module(numrows - 3, 0, pos, 1); module(numrows - 2, 0, pos, 2); module(numrows - 1, 0, pos, 3); module(0, numcols - 2, pos, 4); module(0, numcols - 1, pos, 5); module(1, numcols - 1, pos, 6); module(2, numcols - 1, pos, 7); module(3, numcols - 1, pos, 8); } private void corner4(int pos) { module(numrows - 1, 0, pos, 1); module(numrows - 1, numcols - 1, pos, 2); module(0, numcols - 3, pos, 3); module(0, numcols - 2, pos, 4); module(0, numcols - 1, pos, 5); module(1, numcols - 3, pos, 6); module(1, numcols - 2, pos, 7); module(1, numcols - 1, pos, 8); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/EdifactEncoder.java ================================================ /* * Copyright 2006-2007 Jeremias Maerki. * * 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.datamatrix.encoder; final class EdifactEncoder implements Encoder { @Override public int getEncodingMode() { return HighLevelEncoder.EDIFACT_ENCODATION; } @Override public void encode(EncoderContext context) { //step F StringBuilder buffer = new StringBuilder(); while (context.hasMoreCharacters()) { char c = context.getCurrentChar(); encodeChar(c, buffer); context.pos++; int count = buffer.length(); if (count >= 4) { context.writeCodewords(encodeToCodewords(buffer, 0)); buffer.delete(0, 4); int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode()); if (newMode != getEncodingMode()) { // Return to ASCII encodation, which will actually handle latch to new mode context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION); break; } } } buffer.append((char) 31); //Unlatch handleEOD(context, buffer); } /** * Handle "end of data" situations * * @param context the encoder context * @param buffer the buffer with the remaining encoded characters */ private static void handleEOD(EncoderContext context, CharSequence buffer) { try { int count = buffer.length(); if (count == 0) { return; //Already finished } if (count == 1) { //Only an unlatch at the end context.updateSymbolInfo(); int available = context.getSymbolInfo().getDataCapacity() - context.getCodewordCount(); int remaining = context.getRemainingCharacters(); // The following two lines are a hack inspired by the 'fix' from https://sourceforge.net/p/barcode4j/svn/221/ if (remaining > available) { context.updateSymbolInfo(context.getCodewordCount() + 1); available = context.getSymbolInfo().getDataCapacity() - context.getCodewordCount(); } if (remaining <= available && available <= 2) { return; //No unlatch } } if (count > 4) { throw new IllegalStateException("Count must not exceed 4"); } int restChars = count - 1; String encoded = encodeToCodewords(buffer, 0); boolean endOfSymbolReached = !context.hasMoreCharacters(); boolean restInAscii = endOfSymbolReached && restChars <= 2; if (restChars <= 2) { context.updateSymbolInfo(context.getCodewordCount() + restChars); int available = context.getSymbolInfo().getDataCapacity() - context.getCodewordCount(); if (available >= 3) { restInAscii = false; context.updateSymbolInfo(context.getCodewordCount() + encoded.length()); //available = context.symbolInfo.dataCapacity - context.getCodewordCount(); } } if (restInAscii) { context.resetSymbolInfo(); context.pos -= restChars; } else { context.writeCodewords(encoded); } } finally { context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION); } } private static void encodeChar(char c, StringBuilder sb) { if (c >= ' ' && c <= '?') { sb.append(c); } else if (c >= '@' && c <= '^') { sb.append((char) (c - 64)); } else { HighLevelEncoder.illegalCharacter(c); } } private static String encodeToCodewords(CharSequence sb, int startPos) { int len = sb.length() - startPos; if (len == 0) { throw new IllegalStateException("StringBuilder must not be empty"); } char c1 = sb.charAt(startPos); char c2 = len >= 2 ? sb.charAt(startPos + 1) : 0; char c3 = len >= 3 ? sb.charAt(startPos + 2) : 0; char c4 = len >= 4 ? sb.charAt(startPos + 3) : 0; int v = (c1 << 18) + (c2 << 12) + (c3 << 6) + c4; char cw1 = (char) ((v >> 16) & 255); char cw2 = (char) ((v >> 8) & 255); char cw3 = (char) (v & 255); StringBuilder res = new StringBuilder(3); res.append(cw1); if (len >= 2) { res.append(cw2); } if (len >= 3) { res.append(cw3); } return res.toString(); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/Encoder.java ================================================ /* * Copyright 2006-2007 Jeremias Maerki. * * 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.datamatrix.encoder; interface Encoder { int getEncodingMode(); void encode(EncoderContext context); } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/EncoderContext.java ================================================ /* * Copyright 2006-2007 Jeremias Maerki. * * 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.datamatrix.encoder; import com.google.zxing.Dimension; import java.nio.charset.StandardCharsets; final class EncoderContext { private final String msg; private SymbolShapeHint shape; private Dimension minSize; private Dimension maxSize; private final StringBuilder codewords; int pos; private int newEncoding; private SymbolInfo symbolInfo; private int skipAtEnd; EncoderContext(String msg) { //From this point on Strings are not Unicode anymore! byte[] msgBinary = msg.getBytes(StandardCharsets.ISO_8859_1); StringBuilder sb = new StringBuilder(msgBinary.length); for (int i = 0, c = msgBinary.length; i < c; i++) { char ch = (char) (msgBinary[i] & 0xff); if (ch == '?' && msg.charAt(i) != '?') { throw new IllegalArgumentException("Message contains characters outside ISO-8859-1 encoding."); } sb.append(ch); } this.msg = sb.toString(); //Not Unicode here! shape = SymbolShapeHint.FORCE_NONE; this.codewords = new StringBuilder(msg.length()); newEncoding = -1; } public void setSymbolShape(SymbolShapeHint shape) { this.shape = shape; } public void setSizeConstraints(Dimension minSize, Dimension maxSize) { this.minSize = minSize; this.maxSize = maxSize; } public String getMessage() { return this.msg; } public void setSkipAtEnd(int count) { this.skipAtEnd = count; } public char getCurrentChar() { return msg.charAt(pos); } public char getCurrent() { return msg.charAt(pos); } public StringBuilder getCodewords() { return codewords; } public void writeCodewords(String codewords) { this.codewords.append(codewords); } public void writeCodeword(char codeword) { this.codewords.append(codeword); } public int getCodewordCount() { return this.codewords.length(); } public int getNewEncoding() { return newEncoding; } public void signalEncoderChange(int encoding) { this.newEncoding = encoding; } public void resetEncoderSignal() { this.newEncoding = -1; } public boolean hasMoreCharacters() { return pos < getTotalMessageCharCount(); } private int getTotalMessageCharCount() { return msg.length() - skipAtEnd; } public int getRemainingCharacters() { return getTotalMessageCharCount() - pos; } public SymbolInfo getSymbolInfo() { return symbolInfo; } public void updateSymbolInfo() { updateSymbolInfo(getCodewordCount()); } public void updateSymbolInfo(int len) { if (this.symbolInfo == null || len > this.symbolInfo.getDataCapacity()) { this.symbolInfo = SymbolInfo.lookup(len, shape, minSize, maxSize, true); } } public void resetSymbolInfo() { this.symbolInfo = null; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/ErrorCorrection.java ================================================ /* * Copyright 2006 Jeremias Maerki. * * 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.datamatrix.encoder; /** * Error Correction Code for ECC200. */ public final class ErrorCorrection { /** * Lookup table which factors to use for which number of error correction codewords. * See FACTORS. */ private static final int[] FACTOR_SETS = {5, 7, 10, 11, 12, 14, 18, 20, 24, 28, 36, 42, 48, 56, 62, 68}; /** * Precomputed polynomial factors for ECC 200. */ private static final int[][] FACTORS = { {228, 48, 15, 111, 62}, {23, 68, 144, 134, 240, 92, 254}, {28, 24, 185, 166, 223, 248, 116, 255, 110, 61}, {175, 138, 205, 12, 194, 168, 39, 245, 60, 97, 120}, {41, 153, 158, 91, 61, 42, 142, 213, 97, 178, 100, 242}, {156, 97, 192, 252, 95, 9, 157, 119, 138, 45, 18, 186, 83, 185}, {83, 195, 100, 39, 188, 75, 66, 61, 241, 213, 109, 129, 94, 254, 225, 48, 90, 188}, {15, 195, 244, 9, 233, 71, 168, 2, 188, 160, 153, 145, 253, 79, 108, 82, 27, 174, 186, 172}, {52, 190, 88, 205, 109, 39, 176, 21, 155, 197, 251, 223, 155, 21, 5, 172, 254, 124, 12, 181, 184, 96, 50, 193}, {211, 231, 43, 97, 71, 96, 103, 174, 37, 151, 170, 53, 75, 34, 249, 121, 17, 138, 110, 213, 141, 136, 120, 151, 233, 168, 93, 255}, {245, 127, 242, 218, 130, 250, 162, 181, 102, 120, 84, 179, 220, 251, 80, 182, 229, 18, 2, 4, 68, 33, 101, 137, 95, 119, 115, 44, 175, 184, 59, 25, 225, 98, 81, 112}, {77, 193, 137, 31, 19, 38, 22, 153, 247, 105, 122, 2, 245, 133, 242, 8, 175, 95, 100, 9, 167, 105, 214, 111, 57, 121, 21, 1, 253, 57, 54, 101, 248, 202, 69, 50, 150, 177, 226, 5, 9, 5}, {245, 132, 172, 223, 96, 32, 117, 22, 238, 133, 238, 231, 205, 188, 237, 87, 191, 106, 16, 147, 118, 23, 37, 90, 170, 205, 131, 88, 120, 100, 66, 138, 186, 240, 82, 44, 176, 87, 187, 147, 160, 175, 69, 213, 92, 253, 225, 19}, {175, 9, 223, 238, 12, 17, 220, 208, 100, 29, 175, 170, 230, 192, 215, 235, 150, 159, 36, 223, 38, 200, 132, 54, 228, 146, 218, 234, 117, 203, 29, 232, 144, 238, 22, 150, 201, 117, 62, 207, 164, 13, 137, 245, 127, 67, 247, 28, 155, 43, 203, 107, 233, 53, 143, 46}, {242, 93, 169, 50, 144, 210, 39, 118, 202, 188, 201, 189, 143, 108, 196, 37, 185, 112, 134, 230, 245, 63, 197, 190, 250, 106, 185, 221, 175, 64, 114, 71, 161, 44, 147, 6, 27, 218, 51, 63, 87, 10, 40, 130, 188, 17, 163, 31, 176, 170, 4, 107, 232, 7, 94, 166, 224, 124, 86, 47, 11, 204}, {220, 228, 173, 89, 251, 149, 159, 56, 89, 33, 147, 244, 154, 36, 73, 127, 213, 136, 248, 180, 234, 197, 158, 177, 68, 122, 93, 213, 15, 160, 227, 236, 66, 139, 153, 185, 202, 167, 179, 25, 220, 232, 96, 210, 231, 136, 223, 239, 181, 241, 59, 52, 172, 25, 49, 232, 211, 189, 64, 54, 108, 153, 132, 63, 96, 103, 82, 186}}; private static final int MODULO_VALUE = 0x12D; private static final int[] LOG; private static final int[] ALOG; static { //Create log and antilog table LOG = new int[256]; ALOG = new int[255]; int p = 1; for (int i = 0; i < 255; i++) { ALOG[i] = p; LOG[p] = i; p *= 2; if (p >= 256) { p ^= MODULO_VALUE; } } } private ErrorCorrection() { } /** * Creates the ECC200 error correction for an encoded message. * * @param codewords the codewords * @param symbolInfo information about the symbol to be encoded * @return the codewords with interleaved error correction. */ public static String encodeECC200(String codewords, SymbolInfo symbolInfo) { if (codewords.length() != symbolInfo.getDataCapacity()) { throw new IllegalArgumentException( "The number of codewords does not match the selected symbol"); } StringBuilder sb = new StringBuilder(symbolInfo.getDataCapacity() + symbolInfo.getErrorCodewords()); sb.append(codewords); int blockCount = symbolInfo.getInterleavedBlockCount(); if (blockCount == 1) { String ecc = createECCBlock(codewords, symbolInfo.getErrorCodewords()); sb.append(ecc); } else { sb.setLength(sb.capacity()); int[] dataSizes = new int[blockCount]; int[] errorSizes = new int[blockCount]; int[] startPos = new int[blockCount]; for (int i = 0; i < blockCount; i++) { dataSizes[i] = symbolInfo.getDataLengthForInterleavedBlock(i + 1); errorSizes[i] = symbolInfo.getErrorLengthForInterleavedBlock(i + 1); startPos[i] = 0; if (i > 0) { startPos[i] = startPos[i - 1] + dataSizes[i]; } } for (int block = 0; block < blockCount; block++) { StringBuilder temp = new StringBuilder(dataSizes[block]); for (int d = block; d < symbolInfo.getDataCapacity(); d += blockCount) { temp.append(codewords.charAt(d)); } String ecc = createECCBlock(temp.toString(), errorSizes[block]); int pos = 0; for (int e = block; e < errorSizes[block] * blockCount; e += blockCount) { sb.setCharAt(symbolInfo.getDataCapacity() + e, ecc.charAt(pos++)); } } } return sb.toString(); } private static String createECCBlock(CharSequence codewords, int numECWords) { return createECCBlock(codewords, 0, codewords.length(), numECWords); } private static String createECCBlock(CharSequence codewords, int start, int len, int numECWords) { int table = -1; for (int i = 0; i < FACTOR_SETS.length; i++) { if (FACTOR_SETS[i] == numECWords) { table = i; break; } } if (table < 0) { throw new IllegalArgumentException( "Illegal number of error correction codewords specified: " + numECWords); } int[] poly = FACTORS[table]; char[] ecc = new char[numECWords]; for (int i = 0; i < numECWords; i++) { ecc[i] = 0; } for (int i = start; i < start + len; i++) { int m = ecc[numECWords - 1] ^ codewords.charAt(i); for (int k = numECWords - 1; k > 0; k--) { if (m != 0 && poly[k] != 0) { ecc[k] = (char) (ecc[k - 1] ^ ALOG[(LOG[m] + LOG[poly[k]]) % 255]); } else { ecc[k] = ecc[k - 1]; } } if (m != 0 && poly[0] != 0) { ecc[0] = (char) ALOG[(LOG[m] + LOG[poly[0]]) % 255]; } else { ecc[0] = 0; } } char[] eccReversed = new char[numECWords]; for (int i = 0; i < numECWords; i++) { eccReversed[i] = ecc[numECWords - i - 1]; } return String.valueOf(eccReversed); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/HighLevelEncoder.java ================================================ /* * Copyright 2006-2007 Jeremias Maerki. * * 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.datamatrix.encoder; import com.google.zxing.Dimension; import java.util.Arrays; /** * DataMatrix ECC 200 data encoder following the algorithm described in ISO/IEC 16022:200(E) in * annex S. */ public final class HighLevelEncoder { /** * Padding character */ private static final char PAD = 129; /** * mode latch to C40 encodation mode */ static final char LATCH_TO_C40 = 230; /** * mode latch to Base 256 encodation mode */ static final char LATCH_TO_BASE256 = 231; /** * FNC1 Codeword */ //private static final char FNC1 = 232; /** * Structured Append Codeword */ //private static final char STRUCTURED_APPEND = 233; /** * Reader Programming */ //private static final char READER_PROGRAMMING = 234; /** * Upper Shift */ static final char UPPER_SHIFT = 235; /** * 05 Macro */ private static final char MACRO_05 = 236; /** * 06 Macro */ private static final char MACRO_06 = 237; /** * mode latch to ANSI X.12 encodation mode */ static final char LATCH_TO_ANSIX12 = 238; /** * mode latch to Text encodation mode */ static final char LATCH_TO_TEXT = 239; /** * mode latch to EDIFACT encodation mode */ static final char LATCH_TO_EDIFACT = 240; /** * ECI character (Extended Channel Interpretation) */ //private static final char ECI = 241; /** * Unlatch from C40 encodation */ static final char C40_UNLATCH = 254; /** * Unlatch from X12 encodation */ static final char X12_UNLATCH = 254; /** * 05 Macro header */ private static final String MACRO_05_HEADER = "[)>\u001E05\u001D"; /** * 06 Macro header */ private static final String MACRO_06_HEADER = "[)>\u001E06\u001D"; /** * Macro trailer */ private static final String MACRO_TRAILER = "\u001E\u0004"; static final int ASCII_ENCODATION = 0; static final int C40_ENCODATION = 1; static final int TEXT_ENCODATION = 2; static final int X12_ENCODATION = 3; static final int EDIFACT_ENCODATION = 4; static final int BASE256_ENCODATION = 5; private HighLevelEncoder() { } /* * Converts the message to a byte array using the default encoding (cp437) as defined by the * specification * * @param msg the message * @return the byte array of the message */ /* public static byte[] getBytesForMessage(String msg) { return msg.getBytes(Charset.forName("cp437")); //See 4.4.3 and annex B of ISO/IEC 15438:2001(E) } */ private static char randomize253State(char ch, int codewordPosition) { int pseudoRandom = ((149 * codewordPosition) % 253) + 1; int tempVariable = ch + pseudoRandom; return (char) (tempVariable <= 254 ? tempVariable : tempVariable - 254); } /** * Performs message encoding of a DataMatrix message using the algorithm described in annex P * of ISO/IEC 16022:2000(E). * * @param msg the message * @return the encoded message (the char values range from 0 to 255) */ public static String encodeHighLevel(String msg) { return encodeHighLevel(msg, SymbolShapeHint.FORCE_NONE, null, null); } /** * Performs message encoding of a DataMatrix message using the algorithm described in annex P * of ISO/IEC 16022:2000(E). * * @param msg the message * @param shape requested shape. May be {@code SymbolShapeHint.FORCE_NONE}, * {@code SymbolShapeHint.FORCE_SQUARE} or {@code SymbolShapeHint.FORCE_RECTANGLE}. * @param minSize the minimum symbol size constraint or null for no constraint * @param maxSize the maximum symbol size constraint or null for no constraint * @return the encoded message (the char values range from 0 to 255) */ public static String encodeHighLevel(String msg, SymbolShapeHint shape, Dimension minSize, Dimension maxSize) { //the codewords 0..255 are encoded as Unicode characters Encoder[] encoders = { new ASCIIEncoder(), new C40Encoder(), new TextEncoder(), new X12Encoder(), new EdifactEncoder(), new Base256Encoder() }; EncoderContext context = new EncoderContext(msg); context.setSymbolShape(shape); context.setSizeConstraints(minSize, maxSize); if (msg.startsWith(MACRO_05_HEADER) && msg.endsWith(MACRO_TRAILER)) { context.writeCodeword(MACRO_05); context.setSkipAtEnd(2); context.pos += MACRO_05_HEADER.length(); } else if (msg.startsWith(MACRO_06_HEADER) && msg.endsWith(MACRO_TRAILER)) { context.writeCodeword(MACRO_06); context.setSkipAtEnd(2); context.pos += MACRO_06_HEADER.length(); } int encodingMode = ASCII_ENCODATION; //Default mode while (context.hasMoreCharacters()) { encoders[encodingMode].encode(context); if (context.getNewEncoding() >= 0) { encodingMode = context.getNewEncoding(); context.resetEncoderSignal(); } } int len = context.getCodewordCount(); context.updateSymbolInfo(); int capacity = context.getSymbolInfo().getDataCapacity(); if (len < capacity && encodingMode != ASCII_ENCODATION && encodingMode != BASE256_ENCODATION && encodingMode != EDIFACT_ENCODATION) { context.writeCodeword('\u00fe'); //Unlatch (254) } //Padding StringBuilder codewords = context.getCodewords(); if (codewords.length() < capacity) { codewords.append(PAD); } while (codewords.length() < capacity) { codewords.append(randomize253State(PAD, codewords.length() + 1)); } return context.getCodewords().toString(); } static int lookAheadTest(CharSequence msg, int startpos, int currentMode) { if (startpos >= msg.length()) { return currentMode; } float[] charCounts; //step J if (currentMode == ASCII_ENCODATION) { charCounts = new float[]{0, 1, 1, 1, 1, 1.25f}; } else { charCounts = new float[]{1, 2, 2, 2, 2, 2.25f}; charCounts[currentMode] = 0; } int charsProcessed = 0; while (true) { //step K if ((startpos + charsProcessed) == msg.length()) { int min = Integer.MAX_VALUE; byte[] mins = new byte[6]; int[] intCharCounts = new int[6]; min = findMinimums(charCounts, intCharCounts, min, mins); int minCount = getMinimumCount(mins); if (intCharCounts[ASCII_ENCODATION] == min) { return ASCII_ENCODATION; } if (minCount == 1 && mins[BASE256_ENCODATION] > 0) { return BASE256_ENCODATION; } if (minCount == 1 && mins[EDIFACT_ENCODATION] > 0) { return EDIFACT_ENCODATION; } if (minCount == 1 && mins[TEXT_ENCODATION] > 0) { return TEXT_ENCODATION; } if (minCount == 1 && mins[X12_ENCODATION] > 0) { return X12_ENCODATION; } return C40_ENCODATION; } char c = msg.charAt(startpos + charsProcessed); charsProcessed++; //step L if (isDigit(c)) { charCounts[ASCII_ENCODATION] += 0.5f; } else if (isExtendedASCII(c)) { charCounts[ASCII_ENCODATION] = (float) Math.ceil(charCounts[ASCII_ENCODATION]); charCounts[ASCII_ENCODATION] += 2.0f; } else { charCounts[ASCII_ENCODATION] = (float) Math.ceil(charCounts[ASCII_ENCODATION]); charCounts[ASCII_ENCODATION]++; } //step M if (isNativeC40(c)) { charCounts[C40_ENCODATION] += 2.0f / 3.0f; } else if (isExtendedASCII(c)) { charCounts[C40_ENCODATION] += 8.0f / 3.0f; } else { charCounts[C40_ENCODATION] += 4.0f / 3.0f; } //step N if (isNativeText(c)) { charCounts[TEXT_ENCODATION] += 2.0f / 3.0f; } else if (isExtendedASCII(c)) { charCounts[TEXT_ENCODATION] += 8.0f / 3.0f; } else { charCounts[TEXT_ENCODATION] += 4.0f / 3.0f; } //step O if (isNativeX12(c)) { charCounts[X12_ENCODATION] += 2.0f / 3.0f; } else if (isExtendedASCII(c)) { charCounts[X12_ENCODATION] += 13.0f / 3.0f; } else { charCounts[X12_ENCODATION] += 10.0f / 3.0f; } //step P if (isNativeEDIFACT(c)) { charCounts[EDIFACT_ENCODATION] += 3.0f / 4.0f; } else if (isExtendedASCII(c)) { charCounts[EDIFACT_ENCODATION] += 17.0f / 4.0f; } else { charCounts[EDIFACT_ENCODATION] += 13.0f / 4.0f; } // step Q if (isSpecialB256(c)) { charCounts[BASE256_ENCODATION] += 4.0f; } else { charCounts[BASE256_ENCODATION]++; } //step R if (charsProcessed >= 4) { int[] intCharCounts = new int[6]; byte[] mins = new byte[6]; findMinimums(charCounts, intCharCounts, Integer.MAX_VALUE, mins); int minCount = getMinimumCount(mins); if (intCharCounts[ASCII_ENCODATION] < intCharCounts[BASE256_ENCODATION] && intCharCounts[ASCII_ENCODATION] < intCharCounts[C40_ENCODATION] && intCharCounts[ASCII_ENCODATION] < intCharCounts[TEXT_ENCODATION] && intCharCounts[ASCII_ENCODATION] < intCharCounts[X12_ENCODATION] && intCharCounts[ASCII_ENCODATION] < intCharCounts[EDIFACT_ENCODATION]) { return ASCII_ENCODATION; } if (intCharCounts[BASE256_ENCODATION] < intCharCounts[ASCII_ENCODATION] || (mins[C40_ENCODATION] + mins[TEXT_ENCODATION] + mins[X12_ENCODATION] + mins[EDIFACT_ENCODATION]) == 0) { return BASE256_ENCODATION; } if (minCount == 1 && mins[EDIFACT_ENCODATION] > 0) { return EDIFACT_ENCODATION; } if (minCount == 1 && mins[TEXT_ENCODATION] > 0) { return TEXT_ENCODATION; } if (minCount == 1 && mins[X12_ENCODATION] > 0) { return X12_ENCODATION; } if (intCharCounts[C40_ENCODATION] + 1 < intCharCounts[ASCII_ENCODATION] && intCharCounts[C40_ENCODATION] + 1 < intCharCounts[BASE256_ENCODATION] && intCharCounts[C40_ENCODATION] + 1 < intCharCounts[EDIFACT_ENCODATION] && intCharCounts[C40_ENCODATION] + 1 < intCharCounts[TEXT_ENCODATION]) { if (intCharCounts[C40_ENCODATION] < intCharCounts[X12_ENCODATION]) { return C40_ENCODATION; } if (intCharCounts[C40_ENCODATION] == intCharCounts[X12_ENCODATION]) { int p = startpos + charsProcessed + 1; while (p < msg.length()) { char tc = msg.charAt(p); if (isX12TermSep(tc)) { return X12_ENCODATION; } if (!isNativeX12(tc)) { break; } p++; } return C40_ENCODATION; } } } } } private static int findMinimums(float[] charCounts, int[] intCharCounts, int min, byte[] mins) { Arrays.fill(mins, (byte) 0); for (int i = 0; i < 6; i++) { intCharCounts[i] = (int) Math.ceil(charCounts[i]); int current = intCharCounts[i]; if (min > current) { min = current; Arrays.fill(mins, (byte) 0); } if (min == current) { mins[i]++; } } return min; } private static int getMinimumCount(byte[] mins) { int minCount = 0; for (int i = 0; i < 6; i++) { minCount += mins[i]; } return minCount; } static boolean isDigit(char ch) { return ch >= '0' && ch <= '9'; } static boolean isExtendedASCII(char ch) { return ch >= 128 && ch <= 255; } private static boolean isNativeC40(char ch) { return (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z'); } private static boolean isNativeText(char ch) { return (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z'); } private static boolean isNativeX12(char ch) { return isX12TermSep(ch) || (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z'); } private static boolean isX12TermSep(char ch) { return (ch == '\r') //CR || (ch == '*') || (ch == '>'); } private static boolean isNativeEDIFACT(char ch) { return ch >= ' ' && ch <= '^'; } private static boolean isSpecialB256(char ch) { return false; //TODO NOT IMPLEMENTED YET!!! } /** * Determines the number of consecutive characters that are encodable using numeric compaction. * * @param msg the message * @param startpos the start position within the message * @return the requested character count */ public static int determineConsecutiveDigitCount(CharSequence msg, int startpos) { int count = 0; int len = msg.length(); int idx = startpos; if (idx < len) { char ch = msg.charAt(idx); while (isDigit(ch) && idx < len) { count++; idx++; if (idx < len) { ch = msg.charAt(idx); } } } return count; } static void illegalCharacter(char c) { String hex = Integer.toHexString(c); hex = "0000".substring(0, 4 - hex.length()) + hex; throw new IllegalArgumentException("Illegal character: " + c + " (0x" + hex + ')'); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/SymbolInfo.java ================================================ /* * Copyright 2006 Jeremias Maerki * * 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.datamatrix.encoder; import com.google.zxing.Dimension; /** * Symbol info table for DataMatrix. * * @version $Id$ */ public class SymbolInfo { static final SymbolInfo[] PROD_SYMBOLS = { new SymbolInfo(false, 3, 5, 8, 8, 1), new SymbolInfo(false, 5, 7, 10, 10, 1), /*rect*/new SymbolInfo(true, 5, 7, 16, 6, 1), new SymbolInfo(false, 8, 10, 12, 12, 1), /*rect*/new SymbolInfo(true, 10, 11, 14, 6, 2), new SymbolInfo(false, 12, 12, 14, 14, 1), /*rect*/new SymbolInfo(true, 16, 14, 24, 10, 1), new SymbolInfo(false, 18, 14, 16, 16, 1), new SymbolInfo(false, 22, 18, 18, 18, 1), /*rect*/new SymbolInfo(true, 22, 18, 16, 10, 2), new SymbolInfo(false, 30, 20, 20, 20, 1), /*rect*/new SymbolInfo(true, 32, 24, 16, 14, 2), new SymbolInfo(false, 36, 24, 22, 22, 1), new SymbolInfo(false, 44, 28, 24, 24, 1), /*rect*/new SymbolInfo(true, 49, 28, 22, 14, 2), new SymbolInfo(false, 62, 36, 14, 14, 4), new SymbolInfo(false, 86, 42, 16, 16, 4), new SymbolInfo(false, 114, 48, 18, 18, 4), new SymbolInfo(false, 144, 56, 20, 20, 4), new SymbolInfo(false, 174, 68, 22, 22, 4), new SymbolInfo(false, 204, 84, 24, 24, 4, 102, 42), new SymbolInfo(false, 280, 112, 14, 14, 16, 140, 56), new SymbolInfo(false, 368, 144, 16, 16, 16, 92, 36), new SymbolInfo(false, 456, 192, 18, 18, 16, 114, 48), new SymbolInfo(false, 576, 224, 20, 20, 16, 144, 56), new SymbolInfo(false, 696, 272, 22, 22, 16, 174, 68), new SymbolInfo(false, 816, 336, 24, 24, 16, 136, 56), new SymbolInfo(false, 1050, 408, 18, 18, 36, 175, 68), new SymbolInfo(false, 1304, 496, 20, 20, 36, 163, 62), new DataMatrixSymbolInfo144(), }; private static SymbolInfo[] symbols = PROD_SYMBOLS; private final boolean rectangular; private final int dataCapacity; private final int errorCodewords; public final int matrixWidth; public final int matrixHeight; private final int dataRegions; private final int rsBlockData; private final int rsBlockError; /** * Overrides the symbol info set used by this class. Used for testing purposes. * * @param override the symbol info set to use */ public static void overrideSymbolSet(SymbolInfo[] override) { symbols = override; } public SymbolInfo(boolean rectangular, int dataCapacity, int errorCodewords, int matrixWidth, int matrixHeight, int dataRegions) { this(rectangular, dataCapacity, errorCodewords, matrixWidth, matrixHeight, dataRegions, dataCapacity, errorCodewords); } SymbolInfo(boolean rectangular, int dataCapacity, int errorCodewords, int matrixWidth, int matrixHeight, int dataRegions, int rsBlockData, int rsBlockError) { this.rectangular = rectangular; this.dataCapacity = dataCapacity; this.errorCodewords = errorCodewords; this.matrixWidth = matrixWidth; this.matrixHeight = matrixHeight; this.dataRegions = dataRegions; this.rsBlockData = rsBlockData; this.rsBlockError = rsBlockError; } public static SymbolInfo lookup(int dataCodewords) { return lookup(dataCodewords, SymbolShapeHint.FORCE_NONE, true); } public static SymbolInfo lookup(int dataCodewords, SymbolShapeHint shape) { return lookup(dataCodewords, shape, true); } public static SymbolInfo lookup(int dataCodewords, boolean allowRectangular, boolean fail) { SymbolShapeHint shape = allowRectangular ? SymbolShapeHint.FORCE_NONE : SymbolShapeHint.FORCE_SQUARE; return lookup(dataCodewords, shape, fail); } private static SymbolInfo lookup(int dataCodewords, SymbolShapeHint shape, boolean fail) { return lookup(dataCodewords, shape, null, null, fail); } public static SymbolInfo lookup(int dataCodewords, SymbolShapeHint shape, Dimension minSize, Dimension maxSize, boolean fail) { for (SymbolInfo symbol : symbols) { if (shape == SymbolShapeHint.FORCE_SQUARE && symbol.rectangular) { continue; } if (shape == SymbolShapeHint.FORCE_RECTANGLE && !symbol.rectangular) { continue; } if (minSize != null && (symbol.getSymbolWidth() < minSize.getWidth() || symbol.getSymbolHeight() < minSize.getHeight())) { continue; } if (maxSize != null && (symbol.getSymbolWidth() > maxSize.getWidth() || symbol.getSymbolHeight() > maxSize.getHeight())) { continue; } if (dataCodewords <= symbol.dataCapacity) { return symbol; } } if (fail) { throw new IllegalArgumentException( "Can't find a symbol arrangement that matches the message. Data codewords: " + dataCodewords); } return null; } private int getHorizontalDataRegions() { switch (dataRegions) { case 1: return 1; case 2: case 4: return 2; case 16: return 4; case 36: return 6; default: throw new IllegalStateException("Cannot handle this number of data regions"); } } private int getVerticalDataRegions() { switch (dataRegions) { case 1: case 2: return 1; case 4: return 2; case 16: return 4; case 36: return 6; default: throw new IllegalStateException("Cannot handle this number of data regions"); } } public final int getSymbolDataWidth() { return getHorizontalDataRegions() * matrixWidth; } public final int getSymbolDataHeight() { return getVerticalDataRegions() * matrixHeight; } public final int getSymbolWidth() { return getSymbolDataWidth() + (getHorizontalDataRegions() * 2); } public final int getSymbolHeight() { return getSymbolDataHeight() + (getVerticalDataRegions() * 2); } public int getCodewordCount() { return dataCapacity + errorCodewords; } public int getInterleavedBlockCount() { return dataCapacity / rsBlockData; } public final int getDataCapacity() { return dataCapacity; } public final int getErrorCodewords() { return errorCodewords; } public int getDataLengthForInterleavedBlock(int index) { return rsBlockData; } public final int getErrorLengthForInterleavedBlock(int index) { return rsBlockError; } @Override public final String toString() { return (rectangular ? "Rectangular Symbol:" : "Square Symbol:") + " data region " + matrixWidth + 'x' + matrixHeight + ", symbol size " + getSymbolWidth() + 'x' + getSymbolHeight() + ", symbol data size " + getSymbolDataWidth() + 'x' + getSymbolDataHeight() + ", codewords " + dataCapacity + '+' + errorCodewords; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/SymbolShapeHint.java ================================================ /* * Copyright 2007 Jeremias Maerki. * * 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.datamatrix.encoder; /** * Enumeration for DataMatrix symbol shape hint. It can be used to force square or rectangular * symbols. */ public enum SymbolShapeHint { FORCE_NONE, FORCE_SQUARE, FORCE_RECTANGLE, } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/TextEncoder.java ================================================ /* * Copyright 2006-2007 Jeremias Maerki. * * 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.datamatrix.encoder; final class TextEncoder extends C40Encoder { @Override public int getEncodingMode() { return HighLevelEncoder.TEXT_ENCODATION; } @Override int encodeChar(char c, StringBuilder sb) { if (c == ' ') { sb.append('\3'); return 1; } if (c >= '0' && c <= '9') { sb.append((char) (c - 48 + 4)); return 1; } if (c >= 'a' && c <= 'z') { sb.append((char) (c - 97 + 14)); return 1; } if (c < ' ') { sb.append('\0'); //Shift 1 Set sb.append(c); return 2; } if (c >= '!' && c <= '/') { sb.append('\1'); //Shift 2 Set sb.append((char) (c - 33)); return 2; } if (c >= ':' && c <= '@') { sb.append('\1'); //Shift 2 Set sb.append((char) (c - 58 + 15)); return 2; } if (c >= '[' && c <= '_') { sb.append('\1'); //Shift 2 Set sb.append((char) (c - 91 + 22)); return 2; } if (c == '`') { sb.append('\2'); //Shift 3 Set sb.append((char) (c - 96)); return 2; } if (c >= 'A' && c <= 'Z') { sb.append('\2'); //Shift 3 Set sb.append((char) (c - 65 + 1)); return 2; } if (c >= '{' && c <= 127) { sb.append('\2'); //Shift 3 Set sb.append((char) (c - 123 + 27)); return 2; } sb.append("\1\u001e"); //Shift 2, Upper Shift int len = 2; len += encodeChar((char) (c - 128), sb); return len; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/datamatrix/encoder/X12Encoder.java ================================================ /* * Copyright 2006-2007 Jeremias Maerki. * * 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.datamatrix.encoder; final class X12Encoder extends C40Encoder { @Override public int getEncodingMode() { return HighLevelEncoder.X12_ENCODATION; } @Override public void encode(EncoderContext context) { //step C StringBuilder buffer = new StringBuilder(); while (context.hasMoreCharacters()) { char c = context.getCurrentChar(); context.pos++; encodeChar(c, buffer); int count = buffer.length(); if ((count % 3) == 0) { writeNextTriplet(context, buffer); int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode()); if (newMode != getEncodingMode()) { // Return to ASCII encodation, which will actually handle latch to new mode context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION); break; } } } handleEOD(context, buffer); } @Override int encodeChar(char c, StringBuilder sb) { switch (c) { case '\r': sb.append('\0'); break; case '*': sb.append('\1'); break; case '>': sb.append('\2'); break; case ' ': sb.append('\3'); break; default: if (c >= '0' && c <= '9') { sb.append((char) (c - 48 + 4)); } else if (c >= 'A' && c <= 'Z') { sb.append((char) (c - 65 + 14)); } else { HighLevelEncoder.illegalCharacter(c); } break; } return 1; } @Override void handleEOD(EncoderContext context, StringBuilder buffer) { context.updateSymbolInfo(); int available = context.getSymbolInfo().getDataCapacity() - context.getCodewordCount(); int count = buffer.length(); context.pos -= count; if (context.getRemainingCharacters() > 1 || available > 1 || context.getRemainingCharacters() != available) { context.writeCodeword(HighLevelEncoder.X12_UNLATCH); } if (context.getNewEncoding() < 0) { context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION); } } } ================================================ FILE: scanner/src/main/java/com/google/zxing/maxicode/MaxiCodeReader.java ================================================ /* * Copyright 2011 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.maxicode; import com.google.zxing.BarcodeFormat; import com.google.zxing.BinaryBitmap; import com.google.zxing.ChecksumException; import com.google.zxing.DecodeHintType; import com.google.zxing.FormatException; import com.google.zxing.NotFoundException; import com.google.zxing.Reader; import com.google.zxing.Result; import com.google.zxing.ResultMetadataType; import com.google.zxing.ResultPoint; import com.google.zxing.common.BitMatrix; import com.google.zxing.common.DecoderResult; import com.google.zxing.maxicode.decoder.Decoder; import java.util.Map; /** * This implementation can detect and decode a MaxiCode in an image. */ public final class MaxiCodeReader implements Reader { private static final ResultPoint[] NO_POINTS = new ResultPoint[0]; private static final int MATRIX_WIDTH = 30; private static final int MATRIX_HEIGHT = 33; private final Decoder decoder = new Decoder(); /** * Locates and decodes a MaxiCode in an image. * * @return a String representing the content encoded by the MaxiCode * @throws NotFoundException if a MaxiCode cannot be found * @throws FormatException if a MaxiCode cannot be decoded * @throws ChecksumException if error correction fails */ @Override public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException { return decode(image, null); } @Override public Result decode(BinaryBitmap image, MapMaxiCodes can encode text or structured information as bits in one of several modes, * with multiple character sets in one code. This class decodes the bits back into text.
* * @author mike32767 * @author Manuel Kasten */ final class DecodedBitStreamParser { private static final char SHIFTA = '\uFFF0'; private static final char SHIFTB = '\uFFF1'; private static final char SHIFTC = '\uFFF2'; private static final char SHIFTD = '\uFFF3'; private static final char SHIFTE = '\uFFF4'; private static final char TWOSHIFTA = '\uFFF5'; private static final char THREESHIFTA = '\uFFF6'; private static final char LATCHA = '\uFFF7'; private static final char LATCHB = '\uFFF8'; private static final char LOCK = '\uFFF9'; private static final char ECI = '\uFFFA'; private static final char NS = '\uFFFB'; private static final char PAD = '\uFFFC'; private static final char FS = '\u001C'; private static final char GS = '\u001D'; private static final char RS = '\u001E'; private static final String[] SETS = { "\nABCDEFGHIJKLMNOPQRSTUVWXYZ" + ECI + FS + GS + RS + NS + ' ' + PAD + "\"#$%&'()*+,-./0123456789:" + SHIFTB + SHIFTC + SHIFTD + SHIFTE + LATCHB, "`abcdefghijklmnopqrstuvwxyz" + ECI + FS + GS + RS + NS + '{' + PAD + "}~\u007F;<=>?[\\]^_ ,./:@!|" + PAD + TWOSHIFTA + THREESHIFTA + PAD + SHIFTA + SHIFTC + SHIFTD + SHIFTE + LATCHA, "\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA" + ECI + FS + GS + RS + "\u00DB\u00DC\u00DD\u00DE\u00DF\u00AA\u00AC\u00B1\u00B2\u00B3\u00B5\u00B9\u00BA\u00BC\u00BD\u00BE\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089" + LATCHA + ' ' + LOCK + SHIFTD + SHIFTE + LATCHB, "\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA" + ECI + FS + GS + RS + NS + "\u00FB\u00FC\u00FD\u00FE\u00FF\u00A1\u00A8\u00AB\u00AF\u00B0\u00B4\u00B7\u00B8\u00BB\u00BF\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094" + LATCHA + ' ' + SHIFTC + LOCK + SHIFTE + LATCHB, "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A" + ECI + PAD + PAD + '\u001B' + NS + FS + GS + RS + "\u001F\u009F\u00A0\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A9\u00AD\u00AE\u00B6\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E" + LATCHA + ' ' + SHIFTC + SHIFTD + LOCK + LATCHB, "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u0020\u0021\"\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002A\u002B\u002C\u002D\u002E\u002F\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B\u003C\u003D\u003E\u003F" }; private DecodedBitStreamParser() { } static DecoderResult decode(byte[] bytes, int mode) { StringBuilder result = new StringBuilder(144); switch (mode) { case 2: case 3: String postcode; if (mode == 2) { int pc = getPostCode2(bytes); NumberFormat df = new DecimalFormat("0000000000".substring(0, getPostCode2Length(bytes))); postcode = df.format(pc); } else { postcode = getPostCode3(bytes); } NumberFormat threeDigits = new DecimalFormat("000"); String country = threeDigits.format(getCountry(bytes)); String service = threeDigits.format(getServiceClass(bytes)); result.append(getMessage(bytes, 10, 84)); if (result.toString().startsWith("[)>" + RS + "01" + GS)) { result.insert(9, postcode + GS + country + GS + service + GS); } else { result.insert(0, postcode + GS + country + GS + service + GS); } break; case 4: result.append(getMessage(bytes, 1, 93)); break; case 5: result.append(getMessage(bytes, 1, 77)); break; } return new DecoderResult(bytes, result.toString(), null, String.valueOf(mode)); } private static int getBit(int bit, byte[] bytes) { bit--; return (bytes[bit / 6] & (1 << (5 - (bit % 6)))) == 0 ? 0 : 1; } private static int getInt(byte[] bytes, byte[] x) { if (x.length == 0) { throw new IllegalArgumentException(); } int val = 0; for (int i = 0; i < x.length; i++) { val += getBit(x[i], bytes) << (x.length - i - 1); } return val; } private static int getCountry(byte[] bytes) { return getInt(bytes, new byte[] {53, 54, 43, 44, 45, 46, 47, 48, 37, 38}); } private static int getServiceClass(byte[] bytes) { return getInt(bytes, new byte[] {55, 56, 57, 58, 59, 60, 49, 50, 51, 52}); } private static int getPostCode2Length(byte[] bytes) { return getInt(bytes, new byte[] {39, 40, 41, 42, 31, 32}); } private static int getPostCode2(byte[] bytes) { return getInt(bytes, new byte[] {33, 34, 35, 36, 25, 26, 27, 28, 29, 30, 19, 20, 21, 22, 23, 24, 13, 14, 15, 16, 17, 18, 7, 8, 9, 10, 11, 12, 1, 2}); } private static String getPostCode3(byte[] bytes) { return String.valueOf( new char[] { SETS[0].charAt(getInt(bytes, new byte[] {39, 40, 41, 42, 31, 32})), SETS[0].charAt(getInt(bytes, new byte[] {33, 34, 35, 36, 25, 26})), SETS[0].charAt(getInt(bytes, new byte[] {27, 28, 29, 30, 19, 20})), SETS[0].charAt(getInt(bytes, new byte[] {21, 22, 23, 24, 13, 14})), SETS[0].charAt(getInt(bytes, new byte[] {15, 16, 17, 18, 7, 8})), SETS[0].charAt(getInt(bytes, new byte[] { 9, 10, 11, 12, 1, 2})), } ); } private static String getMessage(byte[] bytes, int start, int len) { StringBuilder sb = new StringBuilder(); int shift = -1; int set = 0; int lastset = 0; for (int i = start; i < start + len; i++) { char c = SETS[set].charAt(bytes[i]); switch (c) { case LATCHA: set = 0; shift = -1; break; case LATCHB: set = 1; shift = -1; break; case SHIFTA: case SHIFTB: case SHIFTC: case SHIFTD: case SHIFTE: lastset = set; set = c - SHIFTA; shift = 1; break; case TWOSHIFTA: lastset = set; set = 0; shift = 2; break; case THREESHIFTA: lastset = set; set = 0; shift = 3; break; case NS: int nsval = (bytes[++i] << 24) + (bytes[++i] << 18) + (bytes[++i] << 12) + (bytes[++i] << 6) + bytes[++i]; sb.append(new DecimalFormat("000000000").format(nsval)); break; case LOCK: shift = -1; break; default: sb.append(c); } if (shift-- == 0) { set = lastset; } } while (sb.length() > 0 && sb.charAt(sb.length() - 1) == PAD) { sb.setLength(sb.length() - 1); } return sb.toString(); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/maxicode/decoder/Decoder.java ================================================ /* * Copyright 2011 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.maxicode.decoder; import com.google.zxing.ChecksumException; import com.google.zxing.DecodeHintType; import com.google.zxing.FormatException; import com.google.zxing.common.BitMatrix; import com.google.zxing.common.DecoderResult; import com.google.zxing.common.reedsolomon.GenericGF; import com.google.zxing.common.reedsolomon.ReedSolomonDecoder; import com.google.zxing.common.reedsolomon.ReedSolomonException; import java.util.Map; /** *The main class which implements MaxiCode decoding -- as opposed to locating and extracting * the MaxiCode from an image.
* * @author Manuel Kasten */ public final class Decoder { private static final int ALL = 0; private static final int EVEN = 1; private static final int ODD = 2; private final ReedSolomonDecoder rsDecoder; public Decoder() { rsDecoder = new ReedSolomonDecoder(GenericGF.MAXICODE_FIELD_64); } public DecoderResult decode(BitMatrix bits) throws ChecksumException, FormatException { return decode(bits, null); } public DecoderResult decode(BitMatrix bits, MapAttempts to locate multiple barcodes in an image by repeatedly decoding portion of the image. * After one barcode is found, the areas left, above, right and below the barcode's * {@link ResultPoint}s are scanned, recursively.
* *A caller may want to also employ {@link ByQuadrantReader} when attempting to find multiple * 2D barcodes, like QR Codes, in an image, where the presence of multiple barcodes might prevent * detecting any one of them.
* *That is, instead of passing a {@link Reader} a caller might pass * {@code new ByQuadrantReader(reader)}.
* * @author Sean Owen */ public final class GenericMultipleBarcodeReader implements MultipleBarcodeReader { private static final int MIN_DIMENSION_TO_RECUR = 100; private static final int MAX_DEPTH = 4; static final Result[] EMPTY_RESULT_ARRAY = new Result[0]; private final Reader delegate; public GenericMultipleBarcodeReader(Reader delegate) { this.delegate = delegate; } @Override public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException { return decodeMultiple(image, null); } @Override public Result[] decodeMultiple(BinaryBitmap image, MapEncapsulates logic that can detect one or more QR Codes in an image, even if the QR Code * is rotated or skewed, or partially obscured.
* * @author Sean Owen * @author Hannes Erven */ public final class MultiDetector extends Detector { private static final DetectorResult[] EMPTY_DETECTOR_RESULTS = new DetectorResult[0]; public MultiDetector(BitMatrix image) { super(image); } public DetectorResult[] detectMulti(MapThis class attempts to find finder patterns in a QR Code. Finder patterns are the square * markers at three corners of a QR Code.
* *This class is thread-safe but not reentrant. Each thread must allocate its own object. * *
In contrast to {@link FinderPatternFinder}, this class will return an array of all possible * QR code locations in the image.
* *Use the TRY_HARDER hint to ask for a more thorough detection.
* * @author Sean Owen * @author Hannes Erven */ final class MultiFinderPatternFinder extends FinderPatternFinder { private static final FinderPatternInfo[] EMPTY_RESULT_ARRAY = new FinderPatternInfo[0]; private static final FinderPattern[][] EMPTY_FP_2D_ARRAY = new FinderPattern[0][]; // TODO MIN_MODULE_COUNT and MAX_MODULE_COUNT would be great hints to ask the user for // since it limits the number of regions to decode // max. legal count of modules per QR code edge (177) private static final float MAX_MODULE_COUNT_PER_EDGE = 180; // min. legal count per modules per QR code edge (11) private static final float MIN_MODULE_COUNT_PER_EDGE = 9; /** * More or less arbitrary cutoff point for determining if two finder patterns might belong * to the same code if they differ less than DIFF_MODSIZE_CUTOFF_PERCENT percent in their * estimated modules sizes. */ private static final float DIFF_MODSIZE_CUTOFF_PERCENT = 0.05f; /** * More or less arbitrary cutoff point for determining if two finder patterns might belong * to the same code if they differ less than DIFF_MODSIZE_CUTOFF pixels/module in their * estimated modules sizes. */ private static final float DIFF_MODSIZE_CUTOFF = 0.5f; /** * A comparator that orders FinderPatterns by their estimated module size. */ private static final class ModuleSizeComparator implements ComparatorCreates a finder that will search the image for three finder patterns.
* * @param image image to search */ MultiFinderPatternFinder(BitMatrix image) { super(image); } MultiFinderPatternFinder(BitMatrix image, ResultPointCallback resultPointCallback) { super(image, resultPointCallback); } /** * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are * those that have been detected at least {@link #CENTER_QUORUM} times, and whose module * size differs from the average among those patterns the least * @throws NotFoundException if 3 such finder patterns do not exist */ private FinderPattern[][] selectMultipleBestPatterns() throws NotFoundException { ListDecodes Codabar barcodes.
* * @author Bas Vijfwinkel * @author David Walker */ public final class CodaBarReader extends OneDReader { // These values are critical for determining how permissive the decoding // will be. All stripe sizes must be within the window these define, as // compared to the average stripe size. private static final float MAX_ACCEPTABLE = 2.0f; private static final float PADDING = 1.5f; private static final String ALPHABET_STRING = "0123456789-$:/.+ABCD"; static final char[] ALPHABET = ALPHABET_STRING.toCharArray(); /** * These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. */ static final int[] CHARACTER_ENCODINGS = { 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD }; // minimal number of characters that should be present (including start and stop characters) // under normal circumstances this should be set to 3, but can be set higher // as a last-ditch attempt to reduce false positives. private static final int MIN_CHARACTER_LENGTH = 3; // official start and end patterns private static final char[] STARTEND_ENCODING = {'A', 'B', 'C', 'D'}; // some Codabar generator allow the Codabar string to be closed by every // character. This will cause lots of false positives! // some industries use a checksum standard but this is not part of the original Codabar standard // for more information see : http://www.mecsw.com/specs/codabar.html // Keep some instance variables to avoid reallocations private final StringBuilder decodeRowResult; private int[] counters; private int counterLength; public CodaBarReader() { decodeRowResult = new StringBuilder(20); counters = new int[80]; counterLength = 0; } @Override public Result decodeRow(int rowNumber, BitArray row, MapDecodes Code 128 barcodes.
* * @author Sean Owen */ public final class Code128Reader extends OneDReader { static final int[][] CODE_PATTERNS = { {2, 1, 2, 2, 2, 2}, // 0 {2, 2, 2, 1, 2, 2}, {2, 2, 2, 2, 2, 1}, {1, 2, 1, 2, 2, 3}, {1, 2, 1, 3, 2, 2}, {1, 3, 1, 2, 2, 2}, // 5 {1, 2, 2, 2, 1, 3}, {1, 2, 2, 3, 1, 2}, {1, 3, 2, 2, 1, 2}, {2, 2, 1, 2, 1, 3}, {2, 2, 1, 3, 1, 2}, // 10 {2, 3, 1, 2, 1, 2}, {1, 1, 2, 2, 3, 2}, {1, 2, 2, 1, 3, 2}, {1, 2, 2, 2, 3, 1}, {1, 1, 3, 2, 2, 2}, // 15 {1, 2, 3, 1, 2, 2}, {1, 2, 3, 2, 2, 1}, {2, 2, 3, 2, 1, 1}, {2, 2, 1, 1, 3, 2}, {2, 2, 1, 2, 3, 1}, // 20 {2, 1, 3, 2, 1, 2}, {2, 2, 3, 1, 1, 2}, {3, 1, 2, 1, 3, 1}, {3, 1, 1, 2, 2, 2}, {3, 2, 1, 1, 2, 2}, // 25 {3, 2, 1, 2, 2, 1}, {3, 1, 2, 2, 1, 2}, {3, 2, 2, 1, 1, 2}, {3, 2, 2, 2, 1, 1}, {2, 1, 2, 1, 2, 3}, // 30 {2, 1, 2, 3, 2, 1}, {2, 3, 2, 1, 2, 1}, {1, 1, 1, 3, 2, 3}, {1, 3, 1, 1, 2, 3}, {1, 3, 1, 3, 2, 1}, // 35 {1, 1, 2, 3, 1, 3}, {1, 3, 2, 1, 1, 3}, {1, 3, 2, 3, 1, 1}, {2, 1, 1, 3, 1, 3}, {2, 3, 1, 1, 1, 3}, // 40 {2, 3, 1, 3, 1, 1}, {1, 1, 2, 1, 3, 3}, {1, 1, 2, 3, 3, 1}, {1, 3, 2, 1, 3, 1}, {1, 1, 3, 1, 2, 3}, // 45 {1, 1, 3, 3, 2, 1}, {1, 3, 3, 1, 2, 1}, {3, 1, 3, 1, 2, 1}, {2, 1, 1, 3, 3, 1}, {2, 3, 1, 1, 3, 1}, // 50 {2, 1, 3, 1, 1, 3}, {2, 1, 3, 3, 1, 1}, {2, 1, 3, 1, 3, 1}, {3, 1, 1, 1, 2, 3}, {3, 1, 1, 3, 2, 1}, // 55 {3, 3, 1, 1, 2, 1}, {3, 1, 2, 1, 1, 3}, {3, 1, 2, 3, 1, 1}, {3, 3, 2, 1, 1, 1}, {3, 1, 4, 1, 1, 1}, // 60 {2, 2, 1, 4, 1, 1}, {4, 3, 1, 1, 1, 1}, {1, 1, 1, 2, 2, 4}, {1, 1, 1, 4, 2, 2}, {1, 2, 1, 1, 2, 4}, // 65 {1, 2, 1, 4, 2, 1}, {1, 4, 1, 1, 2, 2}, {1, 4, 1, 2, 2, 1}, {1, 1, 2, 2, 1, 4}, {1, 1, 2, 4, 1, 2}, // 70 {1, 2, 2, 1, 1, 4}, {1, 2, 2, 4, 1, 1}, {1, 4, 2, 1, 1, 2}, {1, 4, 2, 2, 1, 1}, {2, 4, 1, 2, 1, 1}, // 75 {2, 2, 1, 1, 1, 4}, {4, 1, 3, 1, 1, 1}, {2, 4, 1, 1, 1, 2}, {1, 3, 4, 1, 1, 1}, {1, 1, 1, 2, 4, 2}, // 80 {1, 2, 1, 1, 4, 2}, {1, 2, 1, 2, 4, 1}, {1, 1, 4, 2, 1, 2}, {1, 2, 4, 1, 1, 2}, {1, 2, 4, 2, 1, 1}, // 85 {4, 1, 1, 2, 1, 2}, {4, 2, 1, 1, 1, 2}, {4, 2, 1, 2, 1, 1}, {2, 1, 2, 1, 4, 1}, {2, 1, 4, 1, 2, 1}, // 90 {4, 1, 2, 1, 2, 1}, {1, 1, 1, 1, 4, 3}, {1, 1, 1, 3, 4, 1}, {1, 3, 1, 1, 4, 1}, {1, 1, 4, 1, 1, 3}, // 95 {1, 1, 4, 3, 1, 1}, {4, 1, 1, 1, 1, 3}, {4, 1, 1, 3, 1, 1}, {1, 1, 3, 1, 4, 1}, {1, 1, 4, 1, 3, 1}, // 100 {3, 1, 1, 1, 4, 1}, {4, 1, 1, 1, 3, 1}, {2, 1, 1, 4, 1, 2}, {2, 1, 1, 2, 1, 4}, {2, 1, 1, 2, 3, 2}, // 105 {2, 3, 3, 1, 1, 1, 2} }; private static final float MAX_AVG_VARIANCE = 0.25f; private static final float MAX_INDIVIDUAL_VARIANCE = 0.7f; private static final int CODE_SHIFT = 98; private static final int CODE_CODE_C = 99; private static final int CODE_CODE_B = 100; private static final int CODE_CODE_A = 101; private static final int CODE_FNC_1 = 102; private static final int CODE_FNC_2 = 97; private static final int CODE_FNC_3 = 96; private static final int CODE_FNC_4_A = 101; private static final int CODE_FNC_4_B = 100; private static final int CODE_START_A = 103; private static final int CODE_START_B = 104; private static final int CODE_START_C = 105; private static final int CODE_STOP = 106; private static int[] findStartPattern(BitArray row) throws NotFoundException { int width = row.getSize(); int rowOffset = row.getNextSet(0); int counterPosition = 0; int[] counters = new int[6]; int patternStart = rowOffset; boolean isWhite = false; int patternLength = counters.length; for (int i = rowOffset; i < width; i++) { if (row.get(i) != isWhite) { counters[counterPosition]++; } else { if (counterPosition == patternLength - 1) { float bestVariance = MAX_AVG_VARIANCE; int bestMatch = -1; for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) { float variance = patternMatchVariance(counters, CODE_PATTERNS[startCode], MAX_INDIVIDUAL_VARIANCE); if (variance < bestVariance) { bestVariance = variance; bestMatch = startCode; } } // Look for whitespace before start pattern, >= 50% of width of start pattern if (bestMatch >= 0 && row.isRange(Math.max(0, patternStart - (i - patternStart) / 2), patternStart, false)) { return new int[]{patternStart, i, bestMatch}; } patternStart += counters[0] + counters[1]; System.arraycopy(counters, 2, counters, 0, counterPosition - 1); counters[counterPosition - 1] = 0; counters[counterPosition] = 0; counterPosition--; } else { counterPosition++; } counters[counterPosition] = 1; isWhite = !isWhite; } } throw NotFoundException.getNotFoundInstance(); } private static int decodeCode(BitArray row, int[] counters, int rowOffset) throws NotFoundException { recordPattern(row, rowOffset, counters); float bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept int bestMatch = -1; for (int d = 0; d < CODE_PATTERNS.length; d++) { int[] pattern = CODE_PATTERNS[d]; float variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE); if (variance < bestVariance) { bestVariance = variance; bestMatch = d; } } // TODO We're overlooking the fact that the STOP pattern has 7 values, not 6. if (bestMatch >= 0) { return bestMatch; } else { throw NotFoundException.getNotFoundInstance(); } } @Override public Result decodeRow(int rowNumber, BitArray row, MapDecodes Code 39 barcodes. Supports "Full ASCII Code 39" if USE_CODE_39_EXTENDED_MODE is set.
* * @author Sean Owen * @see Code93Reader */ public final class Code39Reader extends OneDReader { static final String ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%"; /** * These represent the encodings of characters, as patterns of wide and narrow bars. * The 9 least-significant bits of each int correspond to the pattern of wide and narrow, * with 1s representing "wide" and 0s representing narrow. */ static final int[] CHARACTER_ENCODINGS = { 0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8, // U-$ 0x0A2, 0x08A, 0x02A // /-% }; static final int ASTERISK_ENCODING = 0x094; private final boolean usingCheckDigit; private final boolean extendedMode; private final StringBuilder decodeRowResult; private final int[] counters; /** * Creates a reader that assumes all encoded data is data, and does not treat the final * character as a check digit. It will not decoded "extended Code 39" sequences. */ public Code39Reader() { this(false); } /** * Creates a reader that can be configured to check the last character as a check digit. * It will not decoded "extended Code 39" sequences. * * @param usingCheckDigit if true, treat the last data character as a check digit, not * data, and verify that the checksum passes. */ public Code39Reader(boolean usingCheckDigit) { this(usingCheckDigit, false); } /** * Creates a reader that can be configured to check the last character as a check digit, * or optionally attempt to decode "extended Code 39" sequences that are used to encode * the full ASCII character set. * * @param usingCheckDigit if true, treat the last data character as a check digit, not * data, and verify that the checksum passes. * @param extendedMode if true, will attempt to decode extended Code 39 sequences in the * text. */ public Code39Reader(boolean usingCheckDigit, boolean extendedMode) { this.usingCheckDigit = usingCheckDigit; this.extendedMode = extendedMode; decodeRowResult = new StringBuilder(20); counters = new int[9]; } @Override public Result decodeRow(int rowNumber, BitArray row, MapDecodes Code 93 barcodes.
* * @author Sean Owen * @see Code39Reader */ public final class Code93Reader extends OneDReader { // Note that 'abcd' are dummy characters in place of control characters. static final String ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*"; private static final char[] ALPHABET = ALPHABET_STRING.toCharArray(); /** * These represent the encodings of characters, as patterns of wide and narrow bars. * The 9 least-significant bits of each int correspond to the pattern of wide and narrow. */ static final int[] CHARACTER_ENCODINGS = { 0x114, 0x148, 0x144, 0x142, 0x128, 0x124, 0x122, 0x150, 0x112, 0x10A, // 0-9 0x1A8, 0x1A4, 0x1A2, 0x194, 0x192, 0x18A, 0x168, 0x164, 0x162, 0x134, // A-J 0x11A, 0x158, 0x14C, 0x146, 0x12C, 0x116, 0x1B4, 0x1B2, 0x1AC, 0x1A6, // K-T 0x196, 0x19A, 0x16C, 0x166, 0x136, 0x13A, // U-Z 0x12E, 0x1D4, 0x1D2, 0x1CA, 0x16E, 0x176, 0x1AE, // - - % 0x126, 0x1DA, 0x1D6, 0x132, 0x15E, // Control chars? $-* }; static final int ASTERISK_ENCODING = CHARACTER_ENCODINGS[47]; private final StringBuilder decodeRowResult; private final int[] counters; public Code93Reader() { decodeRowResult = new StringBuilder(20); counters = new int[6]; } @Override public Result decodeRow(int rowNumber, BitArray row, MapImplements decoding of the EAN-13 format.
* * @author dswitkin@google.com (Daniel Switkin) * @author Sean Owen * @author alasdair@google.com (Alasdair Mackintosh) */ public final class EAN13Reader extends UPCEANReader { // For an EAN-13 barcode, the first digit is represented by the parities used // to encode the next six digits, according to the table below. For example, // if the barcode is 5 123456 789012 then the value of the first digit is // signified by using odd for '1', even for '2', even for '3', odd for '4', // odd for '5', and even for '6'. See http://en.wikipedia.org/wiki/EAN-13 // // Parity of next 6 digits // Digit 0 1 2 3 4 5 // 0 Odd Odd Odd Odd Odd Odd // 1 Odd Odd Even Odd Even Even // 2 Odd Odd Even Even Odd Even // 3 Odd Odd Even Even Even Odd // 4 Odd Even Odd Odd Even Even // 5 Odd Even Even Odd Odd Even // 6 Odd Even Even Even Odd Odd // 7 Odd Even Odd Even Odd Even // 8 Odd Even Odd Even Even Odd // 9 Odd Even Even Odd Even Odd // // Note that the encoding for '0' uses the same parity as a UPC barcode. Hence // a UPC barcode can be converted to an EAN-13 barcode by prepending a 0. // // The encoding is represented by the following array, which is a bit pattern // using Odd = 0 and Even = 1. For example, 5 is represented by: // // Odd Even Even Odd Odd Even // in binary: // 0 1 1 0 0 1 == 0x19 // static final int[] FIRST_DIGIT_ENCODINGS = { 0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A }; private final int[] decodeMiddleCounters; public EAN13Reader() { decodeMiddleCounters = new int[4]; } @Override protected int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString) throws NotFoundException { int[] counters = decodeMiddleCounters; counters[0] = 0; counters[1] = 0; counters[2] = 0; counters[3] = 0; int end = row.getSize(); int rowOffset = startRange[1]; int lgPatternFound = 0; for (int x = 0; x < 6 && rowOffset < end; x++) { int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS); resultString.append((char) ('0' + bestMatch % 10)); for (int counter : counters) { rowOffset += counter; } if (bestMatch >= 10) { lgPatternFound |= 1 << (5 - x); } } determineFirstDigit(resultString, lgPatternFound); int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN); rowOffset = middleRange[1]; for (int x = 0; x < 6 && rowOffset < end; x++) { int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS); resultString.append((char) ('0' + bestMatch)); for (int counter : counters) { rowOffset += counter; } } return rowOffset; } @Override BarcodeFormat getBarcodeFormat() { return BarcodeFormat.EAN_13; } /** * Based on pattern of odd-even ('L' and 'G') patterns used to encoded the explicitly-encoded * digits in a barcode, determines the implicitly encoded first digit and adds it to the * result string. * * @param resultString string to insert decoded first digit into * @param lgPatternFound int whose bits indicates the pattern of odd/even L/G patterns used to * encode digits * @throws NotFoundException if first digit cannot be determined */ private static void determineFirstDigit(StringBuilder resultString, int lgPatternFound) throws NotFoundException { for (int d = 0; d < 10; d++) { if (lgPatternFound == FIRST_DIGIT_ENCODINGS[d]) { resultString.insert(0, (char) ('0' + d)); return; } } throw NotFoundException.getNotFoundInstance(); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/oned/EAN13Writer.java ================================================ /* * Copyright 2009 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.oned; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.FormatException; import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import java.util.Map; /** * This object renders an EAN13 code as a {@link BitMatrix}. * * @author aripollak@gmail.com (Ari Pollak) */ public final class EAN13Writer extends UPCEANWriter { private static final int CODE_WIDTH = 3 + // start guard (7 * 6) + // left bars 5 + // middle guard (7 * 6) + // right bars 3; // end guard @Override public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, MapImplements decoding of the EAN-8 format.
* * @author Sean Owen */ public final class EAN8Reader extends UPCEANReader { private final int[] decodeMiddleCounters; public EAN8Reader() { decodeMiddleCounters = new int[4]; } @Override protected int decodeMiddle(BitArray row, int[] startRange, StringBuilder result) throws NotFoundException { int[] counters = decodeMiddleCounters; counters[0] = 0; counters[1] = 0; counters[2] = 0; counters[3] = 0; int end = row.getSize(); int rowOffset = startRange[1]; for (int x = 0; x < 4 && rowOffset < end; x++) { int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS); result.append((char) ('0' + bestMatch)); for (int counter : counters) { rowOffset += counter; } } int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN); rowOffset = middleRange[1]; for (int x = 0; x < 4 && rowOffset < end; x++) { int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS); result.append((char) ('0' + bestMatch)); for (int counter : counters) { rowOffset += counter; } } return rowOffset; } @Override BarcodeFormat getBarcodeFormat() { return BarcodeFormat.EAN_8; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/oned/EAN8Writer.java ================================================ /* * Copyright 2009 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.oned; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.FormatException; import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import java.util.Map; /** * This object renders an EAN8 code as a {@link BitMatrix}. * * @author aripollak@gmail.com (Ari Pollak) */ public final class EAN8Writer extends UPCEANWriter { private static final int CODE_WIDTH = 3 + // start guard (7 * 4) + // left bars 5 + // middle guard (7 * 4) + // right bars 3; // end guard @Override public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, MapImplements decoding of the ITF format, or Interleaved Two of Five.
* *This Reader will scan ITF barcodes of certain lengths only. * At the moment it reads length 6, 8, 10, 12, 14, 16, 18, 20, 24, and 44 as these have appeared "in the wild". Not all * lengths are scanned, especially shorter ones, to avoid false positives. This in turn is due to a lack of * required checksum function.
* *The checksum is optional and is not applied by this Reader. The consumer of the decoded * value will have to apply a checksum if required.
* *http://en.wikipedia.org/wiki/Interleaved_2_of_5 * is a great reference for Interleaved 2 of 5 information.
* * @author kevin.osullivan@sita.aero, SITA Lab. */ public final class ITFReader extends OneDReader { private static final float MAX_AVG_VARIANCE = 0.38f; private static final float MAX_INDIVIDUAL_VARIANCE = 0.5f; private static final int W = 3; // Pixel width of a 3x wide line private static final int w = 2; // Pixel width of a 2x wide line private static final int N = 1; // Pixed width of a narrow line /** Valid ITF lengths. Anything longer than the largest value is also allowed. */ private static final int[] DEFAULT_ALLOWED_LENGTHS = {6, 8, 10, 12, 14}; // Stores the actual narrow line width of the image being decoded. private int narrowLineWidth = -1; /** * Start/end guard pattern. * * Note: The end pattern is reversed because the row is reversed before * searching for the END_PATTERN */ private static final int[] START_PATTERN = {N, N, N, N}; private static final int[][] END_PATTERN_REVERSED = { {N, N, w}, // 2x {N, N, W} // 3x }; // See ITFWriter.PATTERNS /** * Patterns of Wide / Narrow lines to indicate each digit */ private static final int[][] PATTERNS = { {N, N, w, w, N}, // 0 {w, N, N, N, w}, // 1 {N, w, N, N, w}, // 2 {w, w, N, N, N}, // 3 {N, N, w, N, w}, // 4 {w, N, w, N, N}, // 5 {N, w, w, N, N}, // 6 {N, N, N, w, w}, // 7 {w, N, N, w, N}, // 8 {N, w, N, w, N}, // 9 {N, N, W, W, N}, // 0 {W, N, N, N, W}, // 1 {N, W, N, N, W}, // 2 {W, W, N, N, N}, // 3 {N, N, W, N, W}, // 4 {W, N, W, N, N}, // 5 {N, W, W, N, N}, // 6 {N, N, N, W, W}, // 7 {W, N, N, W, N}, // 8 {N, W, N, W, N} // 9 }; @Override public Result decodeRow(int rowNumber, BitArray row, MapA reader that can read all available UPC/EAN formats. If a caller wants to try to * read all such formats, it is most efficient to use this implementation rather than invoke * individual readers.
* * @author Sean Owen */ public final class MultiFormatUPCEANReader extends OneDReader { private static final UPCEANReader[] EMPTY_READER_ARRAY = new UPCEANReader[0]; private final UPCEANReader[] readers; public MultiFormatUPCEANReader(MapAttempts to decode a one-dimensional barcode format given a single row of * an image.
* * @param rowNumber row number from top of the row * @param row the black/white pixel data of the row * @param hints decode hints * @return {@link Result} containing encoded string and start/end of barcode * @throws NotFoundException if no potential barcode is found * @throws ChecksumException if a potential barcode is found but does not pass its checksum * @throws FormatException if a potential barcode is found but format is invalid */ public abstract Result decodeRow(int rowNumber, BitArray row, MapEncapsulates functionality and implementation that is common to one-dimensional barcodes.
* * @author dsbnatut@gmail.com (Kazuki Nishiura) */ public abstract class OneDimensionalCodeWriter implements Writer { private static final Pattern NUMERIC = Pattern.compile("[0-9]+"); @Override public final BitMatrix encode(String contents, BarcodeFormat format, int width, int height) throws WriterException { return encode(contents, format, width, height, null); } /** * Encode the contents following specified format. * {@code width} and {@code height} are required size. This method may return bigger size * {@code BitMatrix} when specified size is too small. The user can set both {@code width} and * {@code height} to zero to get minimum size barcode. If negative value is set to {@code width} * or {@code height}, {@code IllegalArgumentException} is thrown. */ @Override public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, MapImplements decoding of the UPC-A format.
* * @author dswitkin@google.com (Daniel Switkin) * @author Sean Owen */ public final class UPCAReader extends UPCEANReader { private final UPCEANReader ean13Reader = new EAN13Reader(); @Override public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, MapEncapsulates functionality and implementation that is common to UPC and EAN families * of one-dimensional barcodes.
* * @author dswitkin@google.com (Daniel Switkin) * @author Sean Owen * @author alasdair@google.com (Alasdair Mackintosh) */ public abstract class UPCEANReader extends OneDReader { // These two values are critical for determining how permissive the decoding will be. // We've arrived at these values through a lot of trial and error. Setting them any higher // lets false positives creep in quickly. private static final float MAX_AVG_VARIANCE = 0.48f; private static final float MAX_INDIVIDUAL_VARIANCE = 0.7f; /** * Start/end guard pattern. */ static final int[] START_END_PATTERN = {1, 1, 1,}; /** * Pattern marking the middle of a UPC/EAN pattern, separating the two halves. */ static final int[] MIDDLE_PATTERN = {1, 1, 1, 1, 1}; /** * end guard pattern. */ static final int[] END_PATTERN = {1, 1, 1, 1, 1, 1}; /** * "Odd", or "L" patterns used to encode UPC/EAN digits. */ static final int[][] L_PATTERNS = { {3, 2, 1, 1}, // 0 {2, 2, 2, 1}, // 1 {2, 1, 2, 2}, // 2 {1, 4, 1, 1}, // 3 {1, 1, 3, 2}, // 4 {1, 2, 3, 1}, // 5 {1, 1, 1, 4}, // 6 {1, 3, 1, 2}, // 7 {1, 2, 1, 3}, // 8 {3, 1, 1, 2} // 9 }; /** * As above but also including the "even", or "G" patterns used to encode UPC/EAN digits. */ static final int[][] L_AND_G_PATTERNS; static { L_AND_G_PATTERNS = new int[20][]; System.arraycopy(L_PATTERNS, 0, L_AND_G_PATTERNS, 0, 10); for (int i = 10; i < 20; i++) { int[] widths = L_PATTERNS[i - 10]; int[] reversedWidths = new int[widths.length]; for (int j = 0; j < widths.length; j++) { reversedWidths[j] = widths[widths.length - j - 1]; } L_AND_G_PATTERNS[i] = reversedWidths; } } private final StringBuilder decodeRowStringBuffer; private final UPCEANExtensionSupport extensionReader; private final EANManufacturerOrgSupport eanManSupport; protected UPCEANReader() { decodeRowStringBuffer = new StringBuilder(20); extensionReader = new UPCEANExtensionSupport(); eanManSupport = new EANManufacturerOrgSupport(); } static int[] findStartGuardPattern(BitArray row) throws NotFoundException { boolean foundStart = false; int[] startRange = null; int nextStart = 0; int[] counters = new int[START_END_PATTERN.length]; while (!foundStart) { Arrays.fill(counters, 0, START_END_PATTERN.length, 0); startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN, counters); int start = startRange[0]; nextStart = startRange[1]; // Make sure there is a quiet zone at least as big as the start pattern before the barcode. // If this check would run off the left edge of the image, do not accept this barcode, // as it is very likely to be a false positive. int quietStart = start - (nextStart - start); if (quietStart >= 0) { foundStart = row.isRange(quietStart, start, false); } } return startRange; } @Override public Result decodeRow(int rowNumber, BitArray row, MapLike {@link #decodeRow(int, BitArray, Map)}, but * allows caller to inform method about where the UPC/EAN start pattern is * found. This allows this to be computed once and reused across many implementations.
* * @param rowNumber row index into the image * @param row encoding of the row of the barcode image * @param startGuardRange start/end column where the opening start pattern was found * @param hints optional hints that influence decoding * @return {@link Result} encapsulating the result of decoding a barcode in the row * @throws NotFoundException if no potential barcode is found * @throws ChecksumException if a potential barcode is found but does not pass its checksum * @throws FormatException if a potential barcode is found but format is invalid */ public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, MapEncapsulates functionality and implementation that is common to UPC and EAN families * of one-dimensional barcodes.
* * @author aripollak@gmail.com (Ari Pollak) * @author dsbnatut@gmail.com (Kazuki Nishiura) */ public abstract class UPCEANWriter extends OneDimensionalCodeWriter { @Override public int getDefaultMargin() { // Use a different default more appropriate for UPC/EAN return 9; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/oned/UPCEReader.java ================================================ /* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.oned; import com.google.zxing.BarcodeFormat; import com.google.zxing.FormatException; import com.google.zxing.NotFoundException; import com.google.zxing.common.BitArray; /** *Implements decoding of the UPC-E format.
*This is a great reference for * UPC-E information.
* * @author Sean Owen */ public final class UPCEReader extends UPCEANReader { /** * The pattern that marks the middle, and end, of a UPC-E pattern. * There is no "second half" to a UPC-E barcode. */ private static final int[] MIDDLE_END_PATTERN = {1, 1, 1, 1, 1, 1}; // For an UPC-E barcode, the final digit is represented by the parities used // to encode the middle six digits, according to the table below. // // Parity of next 6 digits // Digit 0 1 2 3 4 5 // 0 Even Even Even Odd Odd Odd // 1 Even Even Odd Even Odd Odd // 2 Even Even Odd Odd Even Odd // 3 Even Even Odd Odd Odd Even // 4 Even Odd Even Even Odd Odd // 5 Even Odd Odd Even Even Odd // 6 Even Odd Odd Odd Even Even // 7 Even Odd Even Odd Even Odd // 8 Even Odd Even Odd Odd Even // 9 Even Odd Odd Even Odd Even // // The encoding is represented by the following array, which is a bit pattern // using Odd = 0 and Even = 1. For example, 5 is represented by: // // Odd Even Even Odd Odd Even // in binary: // 0 1 1 0 0 1 == 0x19 // /** * See {@link #L_AND_G_PATTERNS}; these values similarly represent patterns of * even-odd parity encodings of digits that imply both the number system (0 or 1) * used, and the check digit. */ static final int[][] NUMSYS_AND_CHECK_DIGIT_PATTERNS = { {0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25}, {0x07, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A} }; private final int[] decodeMiddleCounters; public UPCEReader() { decodeMiddleCounters = new int[4]; } @Override protected int decodeMiddle(BitArray row, int[] startRange, StringBuilder result) throws NotFoundException { int[] counters = decodeMiddleCounters; counters[0] = 0; counters[1] = 0; counters[2] = 0; counters[3] = 0; int end = row.getSize(); int rowOffset = startRange[1]; int lgPatternFound = 0; for (int x = 0; x < 6 && rowOffset < end; x++) { int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS); result.append((char) ('0' + bestMatch % 10)); for (int counter : counters) { rowOffset += counter; } if (bestMatch >= 10) { lgPatternFound |= 1 << (5 - x); } } determineNumSysAndCheckDigit(result, lgPatternFound); return rowOffset; } @Override protected int[] decodeEnd(BitArray row, int endStart) throws NotFoundException { return findGuardPattern(row, endStart, true, MIDDLE_END_PATTERN); } @Override protected boolean checkChecksum(String s) throws FormatException { return super.checkChecksum(convertUPCEtoUPCA(s)); } private static void determineNumSysAndCheckDigit(StringBuilder resultString, int lgPatternFound) throws NotFoundException { for (int numSys = 0; numSys <= 1; numSys++) { for (int d = 0; d < 10; d++) { if (lgPatternFound == NUMSYS_AND_CHECK_DIGIT_PATTERNS[numSys][d]) { resultString.insert(0, (char) ('0' + numSys)); resultString.append((char) ('0' + d)); return; } } } throw NotFoundException.getNotFoundInstance(); } @Override BarcodeFormat getBarcodeFormat() { return BarcodeFormat.UPC_E; } /** * Expands a UPC-E value back into its full, equivalent UPC-A code value. * * @param upce UPC-E code as string of digits * @return equivalent UPC-A code as string of digits */ public static String convertUPCEtoUPCA(String upce) { char[] upceChars = new char[6]; upce.getChars(1, 7, upceChars, 0); StringBuilder result = new StringBuilder(12); result.append(upce.charAt(0)); char lastChar = upceChars[5]; switch (lastChar) { case '0': case '1': case '2': result.append(upceChars, 0, 2); result.append(lastChar); result.append("0000"); result.append(upceChars, 2, 3); break; case '3': result.append(upceChars, 0, 3); result.append("00000"); result.append(upceChars, 3, 2); break; case '4': result.append(upceChars, 0, 4); result.append("00000"); result.append(upceChars[4]); break; default: result.append(upceChars, 0, 5); result.append("0000"); result.append(lastChar); break; } // Only append check digit in conversion if supplied if (upce.length() >= 8) { result.append(upce.charAt(7)); } return result.toString(); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/oned/UPCEWriter.java ================================================ /* * Copyright 2009 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.oned; import java.util.Map; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.FormatException; import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; /** * This object renders an UPC-E code as a {@link BitMatrix}. * * @author 0979097955s@gmail.com (RX) */ public final class UPCEWriter extends UPCEANWriter { private static final int CODE_WIDTH = 3 + // start guard (7 * 6) + // bars 6; // end guard @Override public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, MapThis class contains the methods for decoding the PDF417 codewords.
* * @author SITA Lab (kevin.osullivan@sita.aero) * @author Guenther Grau */ final class DecodedBitStreamParser { private enum Mode { ALPHA, LOWER, MIXED, PUNCT, ALPHA_SHIFT, PUNCT_SHIFT } private static final int TEXT_COMPACTION_MODE_LATCH = 900; private static final int BYTE_COMPACTION_MODE_LATCH = 901; private static final int NUMERIC_COMPACTION_MODE_LATCH = 902; private static final int BYTE_COMPACTION_MODE_LATCH_6 = 924; private static final int ECI_USER_DEFINED = 925; private static final int ECI_GENERAL_PURPOSE = 926; private static final int ECI_CHARSET = 927; private static final int BEGIN_MACRO_PDF417_CONTROL_BLOCK = 928; private static final int BEGIN_MACRO_PDF417_OPTIONAL_FIELD = 923; private static final int MACRO_PDF417_TERMINATOR = 922; private static final int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913; private static final int MAX_NUMERIC_CODEWORDS = 15; private static final int MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME = 0; private static final int MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT = 1; private static final int MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP = 2; private static final int MACRO_PDF417_OPTIONAL_FIELD_SENDER = 3; private static final int MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE = 4; private static final int MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE = 5; private static final int MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM = 6; private static final int PL = 25; private static final int LL = 27; private static final int AS = 27; private static final int ML = 28; private static final int AL = 28; private static final int PS = 29; private static final int PAL = 29; private static final char[] PUNCT_CHARS = ";<>@[\\]_`~!\r\t,:\n-.$/\"|*()?{}'".toCharArray(); private static final char[] MIXED_CHARS = "0123456789&\r\t,:#-.$/+%*=^".toCharArray(); /** * Table containing values for the exponent of 900. * This is used in the numeric compaction decode algorithm. */ private static final BigInteger[] EXP900; static { EXP900 = new BigInteger[16]; EXP900[0] = BigInteger.ONE; BigInteger nineHundred = BigInteger.valueOf(900); EXP900[1] = nineHundred; for (int i = 2; i < EXP900.length; i++) { EXP900[i] = EXP900[i - 1].multiply(nineHundred); } } private static final int NUMBER_OF_SEQUENCE_CODEWORDS = 2; private DecodedBitStreamParser() { } static DecoderResult decode(int[] codewords, String ecLevel) throws FormatException { StringBuilder result = new StringBuilder(codewords.length * 2); Charset encoding = StandardCharsets.ISO_8859_1; // Get compaction mode int codeIndex = 1; int code = codewords[codeIndex++]; PDF417ResultMetadata resultMetadata = new PDF417ResultMetadata(); while (codeIndex < codewords[0]) { switch (code) { case TEXT_COMPACTION_MODE_LATCH: codeIndex = textCompaction(codewords, codeIndex, result); break; case BYTE_COMPACTION_MODE_LATCH: case BYTE_COMPACTION_MODE_LATCH_6: codeIndex = byteCompaction(code, codewords, encoding, codeIndex, result); break; case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: result.append((char) codewords[codeIndex++]); break; case NUMERIC_COMPACTION_MODE_LATCH: codeIndex = numericCompaction(codewords, codeIndex, result); break; case ECI_CHARSET: CharacterSetECI charsetECI = CharacterSetECI.getCharacterSetECIByValue(codewords[codeIndex++]); encoding = Charset.forName(charsetECI.name()); break; case ECI_GENERAL_PURPOSE: // Can't do anything with generic ECI; skip its 2 characters codeIndex += 2; break; case ECI_USER_DEFINED: // Can't do anything with user ECI; skip its 1 character codeIndex++; break; case BEGIN_MACRO_PDF417_CONTROL_BLOCK: codeIndex = decodeMacroBlock(codewords, codeIndex, resultMetadata); break; case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: case MACRO_PDF417_TERMINATOR: // Should not see these outside a macro block throw FormatException.getFormatInstance(); default: // Default to text compaction. During testing numerous barcodes // appeared to be missing the starting mode. In these cases defaulting // to text compaction seems to work. codeIndex--; codeIndex = textCompaction(codewords, codeIndex, result); break; } if (codeIndex < codewords.length) { code = codewords[codeIndex++]; } else { throw FormatException.getFormatInstance(); } } if (result.length() == 0) { throw FormatException.getFormatInstance(); } DecoderResult decoderResult = new DecoderResult(null, result.toString(), null, ecLevel); decoderResult.setOther(resultMetadata); return decoderResult; } @SuppressWarnings("deprecation") static int decodeMacroBlock(int[] codewords, int codeIndex, PDF417ResultMetadata resultMetadata) throws FormatException { if (codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0]) { // we must have at least two bytes left for the segment index throw FormatException.getFormatInstance(); } int[] segmentIndexArray = new int[NUMBER_OF_SEQUENCE_CODEWORDS]; for (int i = 0; i < NUMBER_OF_SEQUENCE_CODEWORDS; i++, codeIndex++) { segmentIndexArray[i] = codewords[codeIndex]; } resultMetadata.setSegmentIndex(Integer.parseInt(decodeBase900toBase10(segmentIndexArray, NUMBER_OF_SEQUENCE_CODEWORDS))); StringBuilder fileId = new StringBuilder(); codeIndex = textCompaction(codewords, codeIndex, fileId); resultMetadata.setFileId(fileId.toString()); int optionalFieldsStart = -1; if (codewords[codeIndex] == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) { optionalFieldsStart = codeIndex + 1; } while (codeIndex < codewords[0]) { switch (codewords[codeIndex]) { case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: codeIndex++; switch (codewords[codeIndex]) { case MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME: StringBuilder fileName = new StringBuilder(); codeIndex = textCompaction(codewords, codeIndex + 1, fileName); resultMetadata.setFileName(fileName.toString()); break; case MACRO_PDF417_OPTIONAL_FIELD_SENDER: StringBuilder sender = new StringBuilder(); codeIndex = textCompaction(codewords, codeIndex + 1, sender); resultMetadata.setSender(sender.toString()); break; case MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE: StringBuilder addressee = new StringBuilder(); codeIndex = textCompaction(codewords, codeIndex + 1, addressee); resultMetadata.setAddressee(addressee.toString()); break; case MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT: StringBuilder segmentCount = new StringBuilder(); codeIndex = numericCompaction(codewords, codeIndex + 1, segmentCount); resultMetadata.setSegmentCount(Integer.parseInt(segmentCount.toString())); break; case MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP: StringBuilder timestamp = new StringBuilder(); codeIndex = numericCompaction(codewords, codeIndex + 1, timestamp); resultMetadata.setTimestamp(Long.parseLong(timestamp.toString())); break; case MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM: StringBuilder checksum = new StringBuilder(); codeIndex = numericCompaction(codewords, codeIndex + 1, checksum); resultMetadata.setChecksum(Integer.parseInt(checksum.toString())); break; case MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE: StringBuilder fileSize = new StringBuilder(); codeIndex = numericCompaction(codewords, codeIndex + 1, fileSize); resultMetadata.setFileSize(Long.parseLong(fileSize.toString())); break; default: throw FormatException.getFormatInstance(); } break; case MACRO_PDF417_TERMINATOR: codeIndex++; resultMetadata.setLastSegment(true); break; default: throw FormatException.getFormatInstance(); } } // copy optional fields to additional options if (optionalFieldsStart != -1) { int optionalFieldsLength = codeIndex - optionalFieldsStart; if (resultMetadata.isLastSegment()) { // do not include terminator optionalFieldsLength--; } resultMetadata.setOptionalData(Arrays.copyOfRange(codewords, optionalFieldsStart, optionalFieldsStart + optionalFieldsLength)); } return codeIndex; } /** * Text Compaction mode (see 5.4.1.5) permits all printable ASCII characters to be * encoded, i.e. values 32 - 126 inclusive in accordance with ISO/IEC 646 (IRV), as * well as selected control characters. * * @param codewords The array of codewords (data + error) * @param codeIndex The current index into the codeword array. * @param result The decoded data is appended to the result. * @return The next index into the codeword array. */ private static int textCompaction(int[] codewords, int codeIndex, StringBuilder result) { // 2 character per codeword int[] textCompactionData = new int[(codewords[0] - codeIndex) * 2]; // Used to hold the byte compaction value if there is a mode shift int[] byteCompactionData = new int[(codewords[0] - codeIndex) * 2]; int index = 0; boolean end = false; while ((codeIndex < codewords[0]) && !end) { int code = codewords[codeIndex++]; if (code < TEXT_COMPACTION_MODE_LATCH) { textCompactionData[index] = code / 30; textCompactionData[index + 1] = code % 30; index += 2; } else { switch (code) { case TEXT_COMPACTION_MODE_LATCH: // reinitialize text compaction mode to alpha sub mode textCompactionData[index++] = TEXT_COMPACTION_MODE_LATCH; break; case BYTE_COMPACTION_MODE_LATCH: case BYTE_COMPACTION_MODE_LATCH_6: case NUMERIC_COMPACTION_MODE_LATCH: case BEGIN_MACRO_PDF417_CONTROL_BLOCK: case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: case MACRO_PDF417_TERMINATOR: codeIndex--; end = true; break; case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: // The Mode Shift codeword 913 shall cause a temporary // switch from Text Compaction mode to Byte Compaction mode. // This switch shall be in effect for only the next codeword, // after which the mode shall revert to the prevailing sub-mode // of the Text Compaction mode. Codeword 913 is only available // in Text Compaction mode; its use is described in 5.4.2.4. textCompactionData[index] = MODE_SHIFT_TO_BYTE_COMPACTION_MODE; code = codewords[codeIndex++]; byteCompactionData[index] = code; index++; break; } } } decodeTextCompaction(textCompactionData, byteCompactionData, index, result); return codeIndex; } /** * The Text Compaction mode includes all the printable ASCII characters * (i.e. values from 32 to 126) and three ASCII control characters: HT or tab * (ASCII value 9), LF or line feed (ASCII value 10), and CR or carriage * return (ASCII value 13). The Text Compaction mode also includes various latch * and shift characters which are used exclusively within the mode. The Text * Compaction mode encodes up to 2 characters per codeword. The compaction rules * for converting data into PDF417 codewords are defined in 5.4.2.2. The sub-mode * switches are defined in 5.4.2.3. * * @param textCompactionData The text compaction data. * @param byteCompactionData The byte compaction data if there * was a mode shift. * @param length The size of the text compaction and byte compaction data. * @param result The decoded data is appended to the result. */ private static void decodeTextCompaction(int[] textCompactionData, int[] byteCompactionData, int length, StringBuilder result) { // Beginning from an initial state of the Alpha sub-mode // The default compaction mode for PDF417 in effect at the start of each symbol shall always be Text // Compaction mode Alpha sub-mode (uppercase alphabetic). A latch codeword from another mode to the Text // Compaction mode shall always switch to the Text Compaction Alpha sub-mode. Mode subMode = Mode.ALPHA; Mode priorToShiftMode = Mode.ALPHA; int i = 0; while (i < length) { int subModeCh = textCompactionData[i]; char ch = 0; switch (subMode) { case ALPHA: // Alpha (uppercase alphabetic) if (subModeCh < 26) { // Upper case Alpha Character ch = (char) ('A' + subModeCh); } else { switch (subModeCh) { case 26: ch = ' '; break; case LL: subMode = Mode.LOWER; break; case ML: subMode = Mode.MIXED; break; case PS: // Shift to punctuation priorToShiftMode = subMode; subMode = Mode.PUNCT_SHIFT; break; case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: result.append((char) byteCompactionData[i]); break; case TEXT_COMPACTION_MODE_LATCH: subMode = Mode.ALPHA; break; } } break; case LOWER: // Lower (lowercase alphabetic) if (subModeCh < 26) { ch = (char) ('a' + subModeCh); } else { switch (subModeCh) { case 26: ch = ' '; break; case AS: // Shift to alpha priorToShiftMode = subMode; subMode = Mode.ALPHA_SHIFT; break; case ML: subMode = Mode.MIXED; break; case PS: // Shift to punctuation priorToShiftMode = subMode; subMode = Mode.PUNCT_SHIFT; break; case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: // TODO Does this need to use the current character encoding? See other occurrences below result.append((char) byteCompactionData[i]); break; case TEXT_COMPACTION_MODE_LATCH: subMode = Mode.ALPHA; break; } } break; case MIXED: // Mixed (numeric and some punctuation) if (subModeCh < PL) { ch = MIXED_CHARS[subModeCh]; } else { switch (subModeCh) { case PL: subMode = Mode.PUNCT; break; case 26: ch = ' '; break; case LL: subMode = Mode.LOWER; break; case AL: subMode = Mode.ALPHA; break; case PS: // Shift to punctuation priorToShiftMode = subMode; subMode = Mode.PUNCT_SHIFT; break; case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: result.append((char) byteCompactionData[i]); break; case TEXT_COMPACTION_MODE_LATCH: subMode = Mode.ALPHA; break; } } break; case PUNCT: // Punctuation if (subModeCh < PAL) { ch = PUNCT_CHARS[subModeCh]; } else { switch (subModeCh) { case PAL: subMode = Mode.ALPHA; break; case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: result.append((char) byteCompactionData[i]); break; case TEXT_COMPACTION_MODE_LATCH: subMode = Mode.ALPHA; break; } } break; case ALPHA_SHIFT: // Restore sub-mode subMode = priorToShiftMode; if (subModeCh < 26) { ch = (char) ('A' + subModeCh); } else { switch (subModeCh) { case 26: ch = ' '; break; case TEXT_COMPACTION_MODE_LATCH: subMode = Mode.ALPHA; break; } } break; case PUNCT_SHIFT: // Restore sub-mode subMode = priorToShiftMode; if (subModeCh < PAL) { ch = PUNCT_CHARS[subModeCh]; } else { switch (subModeCh) { case PAL: subMode = Mode.ALPHA; break; case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: // PS before Shift-to-Byte is used as a padding character, // see 5.4.2.4 of the specification result.append((char) byteCompactionData[i]); break; case TEXT_COMPACTION_MODE_LATCH: subMode = Mode.ALPHA; break; } } break; } if (ch != 0) { // Append decoded character to result result.append(ch); } i++; } } /** * Byte Compaction mode (see 5.4.3) permits all 256 possible 8-bit byte values to be encoded. * This includes all ASCII characters value 0 to 127 inclusive and provides for international * character set support. * * @param mode The byte compaction mode i.e. 901 or 924 * @param codewords The array of codewords (data + error) * @param encoding Currently active character encoding * @param codeIndex The current index into the codeword array. * @param result The decoded data is appended to the result. * @return The next index into the codeword array. */ private static int byteCompaction(int mode, int[] codewords, Charset encoding, int codeIndex, StringBuilder result) { ByteArrayOutputStream decodedBytes = new ByteArrayOutputStream(); int count = 0; long value = 0; boolean end = false; switch (mode) { case BYTE_COMPACTION_MODE_LATCH: // Total number of Byte Compaction characters to be encoded // is not a multiple of 6 int[] byteCompactedCodewords = new int[6]; int nextCode = codewords[codeIndex++]; while ((codeIndex < codewords[0]) && !end) { byteCompactedCodewords[count++] = nextCode; // Base 900 value = 900 * value + nextCode; nextCode = codewords[codeIndex++]; // perhaps it should be ok to check only nextCode >= TEXT_COMPACTION_MODE_LATCH switch (nextCode) { case TEXT_COMPACTION_MODE_LATCH: case BYTE_COMPACTION_MODE_LATCH: case NUMERIC_COMPACTION_MODE_LATCH: case BYTE_COMPACTION_MODE_LATCH_6: case BEGIN_MACRO_PDF417_CONTROL_BLOCK: case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: case MACRO_PDF417_TERMINATOR: codeIndex--; end = true; break; default: if ((count % 5 == 0) && (count > 0)) { // Decode every 5 codewords // Convert to Base 256 for (int j = 0; j < 6; ++j) { decodedBytes.write((byte) (value >> (8 * (5 - j)))); } value = 0; count = 0; } break; } } // if the end of all codewords is reached the last codeword needs to be added if (codeIndex == codewords[0] && nextCode < TEXT_COMPACTION_MODE_LATCH) { byteCompactedCodewords[count++] = nextCode; } // If Byte Compaction mode is invoked with codeword 901, // the last group of codewords is interpreted directly // as one byte per codeword, without compaction. for (int i = 0; i < count; i++) { decodedBytes.write((byte) byteCompactedCodewords[i]); } break; case BYTE_COMPACTION_MODE_LATCH_6: // Total number of Byte Compaction characters to be encoded // is an integer multiple of 6 while (codeIndex < codewords[0] && !end) { int code = codewords[codeIndex++]; if (code < TEXT_COMPACTION_MODE_LATCH) { count++; // Base 900 value = 900 * value + code; } else { switch (code) { case TEXT_COMPACTION_MODE_LATCH: case BYTE_COMPACTION_MODE_LATCH: case NUMERIC_COMPACTION_MODE_LATCH: case BYTE_COMPACTION_MODE_LATCH_6: case BEGIN_MACRO_PDF417_CONTROL_BLOCK: case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: case MACRO_PDF417_TERMINATOR: codeIndex--; end = true; break; } } if ((count % 5 == 0) && (count > 0)) { // Decode every 5 codewords // Convert to Base 256 for (int j = 0; j < 6; ++j) { decodedBytes.write((byte) (value >> (8 * (5 - j)))); } value = 0; count = 0; } } break; } result.append(new String(decodedBytes.toByteArray(), encoding)); return codeIndex; } /** * Numeric Compaction mode (see 5.4.4) permits efficient encoding of numeric data strings. * * @param codewords The array of codewords (data + error) * @param codeIndex The current index into the codeword array. * @param result The decoded data is appended to the result. * @return The next index into the codeword array. */ private static int numericCompaction(int[] codewords, int codeIndex, StringBuilder result) throws FormatException { int count = 0; boolean end = false; int[] numericCodewords = new int[MAX_NUMERIC_CODEWORDS]; while (codeIndex < codewords[0] && !end) { int code = codewords[codeIndex++]; if (codeIndex == codewords[0]) { end = true; } if (code < TEXT_COMPACTION_MODE_LATCH) { numericCodewords[count] = code; count++; } else { switch (code) { case TEXT_COMPACTION_MODE_LATCH: case BYTE_COMPACTION_MODE_LATCH: case BYTE_COMPACTION_MODE_LATCH_6: case BEGIN_MACRO_PDF417_CONTROL_BLOCK: case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: case MACRO_PDF417_TERMINATOR: codeIndex--; end = true; break; } } if ((count % MAX_NUMERIC_CODEWORDS == 0 || code == NUMERIC_COMPACTION_MODE_LATCH || end) && count > 0) { // Re-invoking Numeric Compaction mode (by using codeword 902 // while in Numeric Compaction mode) serves to terminate the // current Numeric Compaction mode grouping as described in 5.4.4.2, // and then to start a new one grouping. result.append(decodeBase900toBase10(numericCodewords, count)); count = 0; } } return codeIndex; } /** * Convert a list of Numeric Compacted codewords from Base 900 to Base 10. * * @param codewords The array of codewords * @param count The number of codewords * @return The decoded string representing the Numeric data. */ /* EXAMPLE Encode the fifteen digit numeric string 000213298174000 Prefix the numeric string with a 1 and set the initial value of t = 1 000 213 298 174 000 Calculate codeword 0 d0 = 1 000 213 298 174 000 mod 900 = 200 t = 1 000 213 298 174 000 div 900 = 1 111 348 109 082 Calculate codeword 1 d1 = 1 111 348 109 082 mod 900 = 282 t = 1 111 348 109 082 div 900 = 1 234 831 232 Calculate codeword 2 d2 = 1 234 831 232 mod 900 = 632 t = 1 234 831 232 div 900 = 1 372 034 Calculate codeword 3 d3 = 1 372 034 mod 900 = 434 t = 1 372 034 div 900 = 1 524 Calculate codeword 4 d4 = 1 524 mod 900 = 624 t = 1 524 div 900 = 1 Calculate codeword 5 d5 = 1 mod 900 = 1 t = 1 div 900 = 0 Codeword sequence is: 1, 624, 434, 632, 282, 200 Decode the above codewords involves 1 x 900 power of 5 + 624 x 900 power of 4 + 434 x 900 power of 3 + 632 x 900 power of 2 + 282 x 900 power of 1 + 200 x 900 power of 0 = 1000213298174000 Remove leading 1 => Result is 000213298174000 */ private static String decodeBase900toBase10(int[] codewords, int count) throws FormatException { BigInteger result = BigInteger.ZERO; for (int i = 0; i < count; i++) { result = result.add(EXP900[count - i - 1].multiply(BigInteger.valueOf(codewords[i]))); } String resultString = result.toString(); if (resultString.charAt(0) != '1') { throw FormatException.getFormatInstance(); } return resultString.substring(1); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/pdf417/decoder/DetectionResult.java ================================================ /* * 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.pdf417.decoder; import com.google.zxing.pdf417.PDF417Common; import java.util.Formatter; /** * @author Guenther Grau */ final class DetectionResult { private static final int ADJUST_ROW_NUMBER_SKIP = 2; private final BarcodeMetadata barcodeMetadata; private final DetectionResultColumn[] detectionResultColumns; private BoundingBox boundingBox; private final int barcodeColumnCount; DetectionResult(BarcodeMetadata barcodeMetadata, BoundingBox boundingBox) { this.barcodeMetadata = barcodeMetadata; this.barcodeColumnCount = barcodeMetadata.getColumnCount(); this.boundingBox = boundingBox; detectionResultColumns = new DetectionResultColumn[barcodeColumnCount + 2]; } DetectionResultColumn[] getDetectionResultColumns() { adjustIndicatorColumnRowNumbers(detectionResultColumns[0]); adjustIndicatorColumnRowNumbers(detectionResultColumns[barcodeColumnCount + 1]); int unadjustedCodewordCount = PDF417Common.MAX_CODEWORDS_IN_BARCODE; int previousUnadjustedCount; do { previousUnadjustedCount = unadjustedCodewordCount; unadjustedCodewordCount = adjustRowNumbers(); } while (unadjustedCodewordCount > 0 && unadjustedCodewordCount < previousUnadjustedCount); return detectionResultColumns; } private void adjustIndicatorColumnRowNumbers(DetectionResultColumn detectionResultColumn) { if (detectionResultColumn != null) { ((DetectionResultRowIndicatorColumn) detectionResultColumn) .adjustCompleteIndicatorColumnRowNumbers(barcodeMetadata); } } // TODO ensure that no detected codewords with unknown row number are left // we should be able to estimate the row height and use it as a hint for the row number // we should also fill the rows top to bottom and bottom to top /** * @return number of codewords which don't have a valid row number. Note that the count is not accurate as codewords * will be counted several times. It just serves as an indicator to see when we can stop adjusting row numbers */ private int adjustRowNumbers() { int unadjustedCount = adjustRowNumbersByRow(); if (unadjustedCount == 0) { return 0; } for (int barcodeColumn = 1; barcodeColumn < barcodeColumnCount + 1; barcodeColumn++) { Codeword[] codewords = detectionResultColumns[barcodeColumn].getCodewords(); for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) { if (codewords[codewordsRow] == null) { continue; } if (!codewords[codewordsRow].hasValidRowNumber()) { adjustRowNumbers(barcodeColumn, codewordsRow, codewords); } } } return unadjustedCount; } private int adjustRowNumbersByRow() { adjustRowNumbersFromBothRI(); // TODO we should only do full row adjustments if row numbers of left and right row indicator column match. // Maybe it's even better to calculated the height (in codeword rows) and divide it by the number of barcode // rows. This, together with the LRI and RRI row numbers should allow us to get a good estimate where a row // number starts and ends. int unadjustedCount = adjustRowNumbersFromLRI(); return unadjustedCount + adjustRowNumbersFromRRI(); } private void adjustRowNumbersFromBothRI() { if (detectionResultColumns[0] == null || detectionResultColumns[barcodeColumnCount + 1] == null) { return; } Codeword[] LRIcodewords = detectionResultColumns[0].getCodewords(); Codeword[] RRIcodewords = detectionResultColumns[barcodeColumnCount + 1].getCodewords(); for (int codewordsRow = 0; codewordsRow < LRIcodewords.length; codewordsRow++) { if (LRIcodewords[codewordsRow] != null && RRIcodewords[codewordsRow] != null && LRIcodewords[codewordsRow].getRowNumber() == RRIcodewords[codewordsRow].getRowNumber()) { for (int barcodeColumn = 1; barcodeColumn <= barcodeColumnCount; barcodeColumn++) { Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; if (codeword == null) { continue; } codeword.setRowNumber(LRIcodewords[codewordsRow].getRowNumber()); if (!codeword.hasValidRowNumber()) { detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow] = null; } } } } } private int adjustRowNumbersFromRRI() { if (detectionResultColumns[barcodeColumnCount + 1] == null) { return 0; } int unadjustedCount = 0; Codeword[] codewords = detectionResultColumns[barcodeColumnCount + 1].getCodewords(); for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) { if (codewords[codewordsRow] == null) { continue; } int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); int invalidRowCounts = 0; for (int barcodeColumn = barcodeColumnCount + 1; barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn--) { Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; if (codeword != null) { invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); if (!codeword.hasValidRowNumber()) { unadjustedCount++; } } } } return unadjustedCount; } private int adjustRowNumbersFromLRI() { if (detectionResultColumns[0] == null) { return 0; } int unadjustedCount = 0; Codeword[] codewords = detectionResultColumns[0].getCodewords(); for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) { if (codewords[codewordsRow] == null) { continue; } int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); int invalidRowCounts = 0; for (int barcodeColumn = 1; barcodeColumn < barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn++) { Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; if (codeword != null) { invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); if (!codeword.hasValidRowNumber()) { unadjustedCount++; } } } } return unadjustedCount; } private static int adjustRowNumberIfValid(int rowIndicatorRowNumber, int invalidRowCounts, Codeword codeword) { if (codeword == null) { return invalidRowCounts; } if (!codeword.hasValidRowNumber()) { if (codeword.isValidRowNumber(rowIndicatorRowNumber)) { codeword.setRowNumber(rowIndicatorRowNumber); invalidRowCounts = 0; } else { ++invalidRowCounts; } } return invalidRowCounts; } private void adjustRowNumbers(int barcodeColumn, int codewordsRow, Codeword[] codewords) { Codeword codeword = codewords[codewordsRow]; Codeword[] previousColumnCodewords = detectionResultColumns[barcodeColumn - 1].getCodewords(); Codeword[] nextColumnCodewords = previousColumnCodewords; if (detectionResultColumns[barcodeColumn + 1] != null) { nextColumnCodewords = detectionResultColumns[barcodeColumn + 1].getCodewords(); } Codeword[] otherCodewords = new Codeword[14]; otherCodewords[2] = previousColumnCodewords[codewordsRow]; otherCodewords[3] = nextColumnCodewords[codewordsRow]; if (codewordsRow > 0) { otherCodewords[0] = codewords[codewordsRow - 1]; otherCodewords[4] = previousColumnCodewords[codewordsRow - 1]; otherCodewords[5] = nextColumnCodewords[codewordsRow - 1]; } if (codewordsRow > 1) { otherCodewords[8] = codewords[codewordsRow - 2]; otherCodewords[10] = previousColumnCodewords[codewordsRow - 2]; otherCodewords[11] = nextColumnCodewords[codewordsRow - 2]; } if (codewordsRow < codewords.length - 1) { otherCodewords[1] = codewords[codewordsRow + 1]; otherCodewords[6] = previousColumnCodewords[codewordsRow + 1]; otherCodewords[7] = nextColumnCodewords[codewordsRow + 1]; } if (codewordsRow < codewords.length - 2) { otherCodewords[9] = codewords[codewordsRow + 2]; otherCodewords[12] = previousColumnCodewords[codewordsRow + 2]; otherCodewords[13] = nextColumnCodewords[codewordsRow + 2]; } for (Codeword otherCodeword : otherCodewords) { if (adjustRowNumber(codeword, otherCodeword)) { return; } } } /** * @return true, if row number was adjusted, false otherwise */ private static boolean adjustRowNumber(Codeword codeword, Codeword otherCodeword) { if (otherCodeword == null) { return false; } if (otherCodeword.hasValidRowNumber() && otherCodeword.getBucket() == codeword.getBucket()) { codeword.setRowNumber(otherCodeword.getRowNumber()); return true; } return false; } int getBarcodeColumnCount() { return barcodeColumnCount; } int getBarcodeRowCount() { return barcodeMetadata.getRowCount(); } int getBarcodeECLevel() { return barcodeMetadata.getErrorCorrectionLevel(); } void setBoundingBox(BoundingBox boundingBox) { this.boundingBox = boundingBox; } BoundingBox getBoundingBox() { return boundingBox; } void setDetectionResultColumn(int barcodeColumn, DetectionResultColumn detectionResultColumn) { detectionResultColumns[barcodeColumn] = detectionResultColumn; } DetectionResultColumn getDetectionResultColumn(int barcodeColumn) { return detectionResultColumns[barcodeColumn]; } @Override public String toString() { DetectionResultColumn rowIndicatorColumn = detectionResultColumns[0]; if (rowIndicatorColumn == null) { rowIndicatorColumn = detectionResultColumns[barcodeColumnCount + 1]; } try (Formatter formatter = new Formatter()) { for (int codewordsRow = 0; codewordsRow < rowIndicatorColumn.getCodewords().length; codewordsRow++) { formatter.format("CW %3d:", codewordsRow); for (int barcodeColumn = 0; barcodeColumn < barcodeColumnCount + 2; barcodeColumn++) { if (detectionResultColumns[barcodeColumn] == null) { formatter.format(" | "); continue; } Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; if (codeword == null) { formatter.format(" | "); continue; } formatter.format(" %3d|%3d", codeword.getRowNumber(), codeword.getValue()); } formatter.format("%n"); } return formatter.toString(); } } } ================================================ FILE: scanner/src/main/java/com/google/zxing/pdf417/decoder/DetectionResultColumn.java ================================================ /* * 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.pdf417.decoder; import java.util.Formatter; /** * @author Guenther Grau */ class DetectionResultColumn { private static final int MAX_NEARBY_DISTANCE = 5; private final BoundingBox boundingBox; private final Codeword[] codewords; DetectionResultColumn(BoundingBox boundingBox) { this.boundingBox = new BoundingBox(boundingBox); codewords = new Codeword[boundingBox.getMaxY() - boundingBox.getMinY() + 1]; } final Codeword getCodewordNearby(int imageRow) { Codeword codeword = getCodeword(imageRow); if (codeword != null) { return codeword; } for (int i = 1; i < MAX_NEARBY_DISTANCE; i++) { int nearImageRow = imageRowToCodewordIndex(imageRow) - i; if (nearImageRow >= 0) { codeword = codewords[nearImageRow]; if (codeword != null) { return codeword; } } nearImageRow = imageRowToCodewordIndex(imageRow) + i; if (nearImageRow < codewords.length) { codeword = codewords[nearImageRow]; if (codeword != null) { return codeword; } } } return null; } final int imageRowToCodewordIndex(int imageRow) { return imageRow - boundingBox.getMinY(); } final void setCodeword(int imageRow, Codeword codeword) { codewords[imageRowToCodewordIndex(imageRow)] = codeword; } final Codeword getCodeword(int imageRow) { return codewords[imageRowToCodewordIndex(imageRow)]; } final BoundingBox getBoundingBox() { return boundingBox; } final Codeword[] getCodewords() { return codewords; } @Override public String toString() { try (Formatter formatter = new Formatter()) { int row = 0; for (Codeword codeword : codewords) { if (codeword == null) { formatter.format("%3d: | %n", row++); continue; } formatter.format("%3d: %3d|%3d%n", row++, codeword.getRowNumber(), codeword.getValue()); } return formatter.toString(); } } } ================================================ FILE: scanner/src/main/java/com/google/zxing/pdf417/decoder/DetectionResultRowIndicatorColumn.java ================================================ /* * 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.pdf417.decoder; import com.google.zxing.ResultPoint; import com.google.zxing.pdf417.PDF417Common; /** * @author Guenther Grau */ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn { private final boolean isLeft; DetectionResultRowIndicatorColumn(BoundingBox boundingBox, boolean isLeft) { super(boundingBox); this.isLeft = isLeft; } private void setRowNumbers() { for (Codeword codeword : getCodewords()) { if (codeword != null) { codeword.setRowNumberAsRowIndicatorColumn(); } } } // TODO implement properly // TODO maybe we should add missing codewords to store the correct row number to make // finding row numbers for other columns easier // use row height count to make detection of invalid row numbers more reliable void adjustCompleteIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) { Codeword[] codewords = getCodewords(); setRowNumbers(); removeIncorrectCodewords(codewords, barcodeMetadata); BoundingBox boundingBox = getBoundingBox(); ResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight(); ResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight(); int firstRow = imageRowToCodewordIndex((int) top.getY()); int lastRow = imageRowToCodewordIndex((int) bottom.getY()); // We need to be careful using the average row height. Barcode could be skewed so that we have smaller and // taller rows //float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount(); int barcodeRow = -1; int maxRowHeight = 1; int currentRowHeight = 0; for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) { if (codewords[codewordsRow] == null) { continue; } Codeword codeword = codewords[codewordsRow]; // float expectedRowNumber = (codewordsRow - firstRow) / averageRowHeight; // if (Math.abs(codeword.getRowNumber() - expectedRowNumber) > 2) { // SimpleLog.log(LEVEL.WARNING, // "Removing codeword, rowNumberSkew too high, codeword[" + codewordsRow + "]: Expected Row: " + // expectedRowNumber + ", RealRow: " + codeword.getRowNumber() + ", value: " + codeword.getValue()); // codewords[codewordsRow] = null; // } int rowDifference = codeword.getRowNumber() - barcodeRow; // TODO improve handling with case where first row indicator doesn't start with 0 if (rowDifference == 0) { currentRowHeight++; } else if (rowDifference == 1) { maxRowHeight = Math.max(maxRowHeight, currentRowHeight); currentRowHeight = 1; barcodeRow = codeword.getRowNumber(); } else if (rowDifference < 0 || codeword.getRowNumber() >= barcodeMetadata.getRowCount() || rowDifference > codewordsRow) { codewords[codewordsRow] = null; } else { int checkedRows; if (maxRowHeight > 2) { checkedRows = (maxRowHeight - 2) * rowDifference; } else { checkedRows = rowDifference; } boolean closePreviousCodewordFound = checkedRows >= codewordsRow; for (int i = 1; i <= checkedRows && !closePreviousCodewordFound; i++) { // there must be (height * rowDifference) number of codewords missing. For now we assume height = 1. // This should hopefully get rid of most problems already. closePreviousCodewordFound = codewords[codewordsRow - i] != null; } if (closePreviousCodewordFound) { codewords[codewordsRow] = null; } else { barcodeRow = codeword.getRowNumber(); currentRowHeight = 1; } } } //return (int) (averageRowHeight + 0.5); } int[] getRowHeights() { BarcodeMetadata barcodeMetadata = getBarcodeMetadata(); if (barcodeMetadata == null) { return null; } adjustIncompleteIndicatorColumnRowNumbers(barcodeMetadata); int[] result = new int[barcodeMetadata.getRowCount()]; for (Codeword codeword : getCodewords()) { if (codeword != null) { int rowNumber = codeword.getRowNumber(); if (rowNumber >= result.length) { // We have more rows than the barcode metadata allows for, ignore them. continue; } result[rowNumber]++; } // else throw exception? } return result; } // TODO maybe we should add missing codewords to store the correct row number to make // finding row numbers for other columns easier // use row height count to make detection of invalid row numbers more reliable private void adjustIncompleteIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) { BoundingBox boundingBox = getBoundingBox(); ResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight(); ResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight(); int firstRow = imageRowToCodewordIndex((int) top.getY()); int lastRow = imageRowToCodewordIndex((int) bottom.getY()); //float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount(); Codeword[] codewords = getCodewords(); int barcodeRow = -1; int maxRowHeight = 1; int currentRowHeight = 0; for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) { if (codewords[codewordsRow] == null) { continue; } Codeword codeword = codewords[codewordsRow]; codeword.setRowNumberAsRowIndicatorColumn(); int rowDifference = codeword.getRowNumber() - barcodeRow; // TODO improve handling with case where first row indicator doesn't start with 0 if (rowDifference == 0) { currentRowHeight++; } else if (rowDifference == 1) { maxRowHeight = Math.max(maxRowHeight, currentRowHeight); currentRowHeight = 1; barcodeRow = codeword.getRowNumber(); } else if (codeword.getRowNumber() >= barcodeMetadata.getRowCount()) { codewords[codewordsRow] = null; } else { barcodeRow = codeword.getRowNumber(); currentRowHeight = 1; } } //return (int) (averageRowHeight + 0.5); } BarcodeMetadata getBarcodeMetadata() { Codeword[] codewords = getCodewords(); BarcodeValue barcodeColumnCount = new BarcodeValue(); BarcodeValue barcodeRowCountUpperPart = new BarcodeValue(); BarcodeValue barcodeRowCountLowerPart = new BarcodeValue(); BarcodeValue barcodeECLevel = new BarcodeValue(); for (Codeword codeword : codewords) { if (codeword == null) { continue; } codeword.setRowNumberAsRowIndicatorColumn(); int rowIndicatorValue = codeword.getValue() % 30; int codewordRowNumber = codeword.getRowNumber(); if (!isLeft) { codewordRowNumber += 2; } switch (codewordRowNumber % 3) { case 0: barcodeRowCountUpperPart.setValue(rowIndicatorValue * 3 + 1); break; case 1: barcodeECLevel.setValue(rowIndicatorValue / 3); barcodeRowCountLowerPart.setValue(rowIndicatorValue % 3); break; case 2: barcodeColumnCount.setValue(rowIndicatorValue + 1); break; } } // Maybe we should check if we have ambiguous values? if ((barcodeColumnCount.getValue().length == 0) || (barcodeRowCountUpperPart.getValue().length == 0) || (barcodeRowCountLowerPart.getValue().length == 0) || (barcodeECLevel.getValue().length == 0) || barcodeColumnCount.getValue()[0] < 1 || barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] < PDF417Common.MIN_ROWS_IN_BARCODE || barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] > PDF417Common.MAX_ROWS_IN_BARCODE) { return null; } BarcodeMetadata barcodeMetadata = new BarcodeMetadata(barcodeColumnCount.getValue()[0], barcodeRowCountUpperPart.getValue()[0], barcodeRowCountLowerPart.getValue()[0], barcodeECLevel.getValue()[0]); removeIncorrectCodewords(codewords, barcodeMetadata); return barcodeMetadata; } private void removeIncorrectCodewords(Codeword[] codewords, BarcodeMetadata barcodeMetadata) { // Remove codewords which do not match the metadata // TODO Maybe we should keep the incorrect codewords for the start and end positions? for (int codewordRow = 0; codewordRow < codewords.length; codewordRow++) { Codeword codeword = codewords[codewordRow]; if (codewords[codewordRow] == null) { continue; } int rowIndicatorValue = codeword.getValue() % 30; int codewordRowNumber = codeword.getRowNumber(); if (codewordRowNumber > barcodeMetadata.getRowCount()) { codewords[codewordRow] = null; continue; } if (!isLeft) { codewordRowNumber += 2; } switch (codewordRowNumber % 3) { case 0: if (rowIndicatorValue * 3 + 1 != barcodeMetadata.getRowCountUpperPart()) { codewords[codewordRow] = null; } break; case 1: if (rowIndicatorValue / 3 != barcodeMetadata.getErrorCorrectionLevel() || rowIndicatorValue % 3 != barcodeMetadata.getRowCountLowerPart()) { codewords[codewordRow] = null; } break; case 2: if (rowIndicatorValue + 1 != barcodeMetadata.getColumnCount()) { codewords[codewordRow] = null; } break; } } } boolean isLeft() { return isLeft; } @Override public String toString() { return "IsLeft: " + isLeft + '\n' + super.toString(); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/pdf417/decoder/PDF417CodewordDecoder.java ================================================ /* * 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.pdf417.decoder; import com.google.zxing.common.detector.MathUtils; import com.google.zxing.pdf417.PDF417Common; /** * @author Guenther Grau * @author creatale GmbH (christoph.schulz@creatale.de) */ final class PDF417CodewordDecoder { private static final float[][] RATIOS_TABLE = new float[PDF417Common.SYMBOL_TABLE.length][PDF417Common.BARS_IN_MODULE]; static { // Pre-computes the symbol ratio table. for (int i = 0; i < PDF417Common.SYMBOL_TABLE.length; i++) { int currentSymbol = PDF417Common.SYMBOL_TABLE[i]; int currentBit = currentSymbol & 0x1; for (int j = 0; j < PDF417Common.BARS_IN_MODULE; j++) { float size = 0.0f; while ((currentSymbol & 0x1) == currentBit) { size += 1.0f; currentSymbol >>= 1; } currentBit = currentSymbol & 0x1; RATIOS_TABLE[i][PDF417Common.BARS_IN_MODULE - j - 1] = size / PDF417Common.MODULES_IN_CODEWORD; } } } private PDF417CodewordDecoder() { } static int getDecodedValue(int[] moduleBitCount) { int decodedValue = getDecodedCodewordValue(sampleBitCounts(moduleBitCount)); if (decodedValue != -1) { return decodedValue; } return getClosestDecodedValue(moduleBitCount); } private static int[] sampleBitCounts(int[] moduleBitCount) { float bitCountSum = MathUtils.sum(moduleBitCount); int[] result = new int[PDF417Common.BARS_IN_MODULE]; int bitCountIndex = 0; int sumPreviousBits = 0; for (int i = 0; i < PDF417Common.MODULES_IN_CODEWORD; i++) { float sampleIndex = bitCountSum / (2 * PDF417Common.MODULES_IN_CODEWORD) + (i * bitCountSum) / PDF417Common.MODULES_IN_CODEWORD; if (sumPreviousBits + moduleBitCount[bitCountIndex] <= sampleIndex) { sumPreviousBits += moduleBitCount[bitCountIndex]; bitCountIndex++; } result[bitCountIndex]++; } return result; } private static int getDecodedCodewordValue(int[] moduleBitCount) { int decodedValue = getBitValue(moduleBitCount); return PDF417Common.getCodeword(decodedValue) == -1 ? -1 : decodedValue; } private static int getBitValue(int[] moduleBitCount) { long result = 0; for (int i = 0; i < moduleBitCount.length; i++) { for (int bit = 0; bit < moduleBitCount[i]; bit++) { result = (result << 1) | (i % 2 == 0 ? 1 : 0); } } return (int) result; } private static int getClosestDecodedValue(int[] moduleBitCount) { int bitCountSum = MathUtils.sum(moduleBitCount); float[] bitCountRatios = new float[PDF417Common.BARS_IN_MODULE]; if (bitCountSum > 1) { for (int i = 0; i < bitCountRatios.length; i++) { bitCountRatios[i] = moduleBitCount[i] / (float) bitCountSum; } } float bestMatchError = Float.MAX_VALUE; int bestMatch = -1; for (int j = 0; j < RATIOS_TABLE.length; j++) { float error = 0.0f; float[] ratioTableRow = RATIOS_TABLE[j]; for (int k = 0; k < PDF417Common.BARS_IN_MODULE; k++) { float diff = ratioTableRow[k] - bitCountRatios[k]; error += diff * diff; if (error >= bestMatchError) { break; } } if (error < bestMatchError) { bestMatchError = error; bestMatch = PDF417Common.SYMBOL_TABLE[j]; } } return bestMatch; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/pdf417/decoder/PDF417ScanningDecoder.java ================================================ /* * 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.pdf417.decoder; import com.google.zxing.ChecksumException; import com.google.zxing.FormatException; import com.google.zxing.NotFoundException; import com.google.zxing.ResultPoint; import com.google.zxing.common.BitMatrix; import com.google.zxing.common.DecoderResult; import com.google.zxing.common.detector.MathUtils; import com.google.zxing.pdf417.PDF417Common; import com.google.zxing.pdf417.decoder.ec.ErrorCorrection; import java.util.ArrayList; import java.util.Collection; import java.util.Formatter; import java.util.List; /** * @author Guenther Grau */ public final class PDF417ScanningDecoder { private static final int CODEWORD_SKEW_SIZE = 2; private static final int MAX_ERRORS = 3; private static final int MAX_EC_CODEWORDS = 512; private static final ErrorCorrection errorCorrection = new ErrorCorrection(); private PDF417ScanningDecoder() { } // TODO don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern // columns. That way width can be deducted from the pattern column. // This approach also allows to detect more details about the barcode, e.g. if a bar type (white or black) is wider // than it should be. This can happen if the scanner used a bad blackpoint. public static DecoderResult decode(BitMatrix image, ResultPoint imageTopLeft, ResultPoint imageBottomLeft, ResultPoint imageTopRight, ResultPoint imageBottomRight, int minCodewordWidth, int maxCodewordWidth) throws NotFoundException, FormatException, ChecksumException { BoundingBox boundingBox = new BoundingBox(image, imageTopLeft, imageBottomLeft, imageTopRight, imageBottomRight); DetectionResultRowIndicatorColumn leftRowIndicatorColumn = null; DetectionResultRowIndicatorColumn rightRowIndicatorColumn = null; DetectionResult detectionResult; for (boolean firstPass = true; ; firstPass = false) { if (imageTopLeft != null) { leftRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopLeft, true, minCodewordWidth, maxCodewordWidth); } if (imageTopRight != null) { rightRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopRight, false, minCodewordWidth, maxCodewordWidth); } detectionResult = merge(leftRowIndicatorColumn, rightRowIndicatorColumn); if (detectionResult == null) { throw NotFoundException.getNotFoundInstance(); } BoundingBox resultBox = detectionResult.getBoundingBox(); if (firstPass && resultBox != null && (resultBox.getMinY() < boundingBox.getMinY() || resultBox.getMaxY() > boundingBox.getMaxY())) { boundingBox = resultBox; } else { break; } } detectionResult.setBoundingBox(boundingBox); int maxBarcodeColumn = detectionResult.getBarcodeColumnCount() + 1; detectionResult.setDetectionResultColumn(0, leftRowIndicatorColumn); detectionResult.setDetectionResultColumn(maxBarcodeColumn, rightRowIndicatorColumn); boolean leftToRight = leftRowIndicatorColumn != null; for (int barcodeColumnCount = 1; barcodeColumnCount <= maxBarcodeColumn; barcodeColumnCount++) { int barcodeColumn = leftToRight ? barcodeColumnCount : maxBarcodeColumn - barcodeColumnCount; if (detectionResult.getDetectionResultColumn(barcodeColumn) != null) { // This will be the case for the opposite row indicator column, which doesn't need to be decoded again. continue; } DetectionResultColumn detectionResultColumn; if (barcodeColumn == 0 || barcodeColumn == maxBarcodeColumn) { detectionResultColumn = new DetectionResultRowIndicatorColumn(boundingBox, barcodeColumn == 0); } else { detectionResultColumn = new DetectionResultColumn(boundingBox); } detectionResult.setDetectionResultColumn(barcodeColumn, detectionResultColumn); int startColumn = -1; int previousStartColumn = startColumn; // TODO start at a row for which we know the start position, then detect upwards and downwards from there. for (int imageRow = boundingBox.getMinY(); imageRow <= boundingBox.getMaxY(); imageRow++) { startColumn = getStartColumn(detectionResult, barcodeColumn, imageRow, leftToRight); if (startColumn < 0 || startColumn > boundingBox.getMaxX()) { if (previousStartColumn == -1) { continue; } startColumn = previousStartColumn; } Codeword codeword = detectCodeword(image, boundingBox.getMinX(), boundingBox.getMaxX(), leftToRight, startColumn, imageRow, minCodewordWidth, maxCodewordWidth); if (codeword != null) { detectionResultColumn.setCodeword(imageRow, codeword); previousStartColumn = startColumn; minCodewordWidth = Math.min(minCodewordWidth, codeword.getWidth()); maxCodewordWidth = Math.max(maxCodewordWidth, codeword.getWidth()); } } } return createDecoderResult(detectionResult); } private static DetectionResult merge(DetectionResultRowIndicatorColumn leftRowIndicatorColumn, DetectionResultRowIndicatorColumn rightRowIndicatorColumn) throws NotFoundException { if (leftRowIndicatorColumn == null && rightRowIndicatorColumn == null) { return null; } BarcodeMetadata barcodeMetadata = getBarcodeMetadata(leftRowIndicatorColumn, rightRowIndicatorColumn); if (barcodeMetadata == null) { return null; } BoundingBox boundingBox = BoundingBox.merge(adjustBoundingBox(leftRowIndicatorColumn), adjustBoundingBox(rightRowIndicatorColumn)); return new DetectionResult(barcodeMetadata, boundingBox); } private static BoundingBox adjustBoundingBox(DetectionResultRowIndicatorColumn rowIndicatorColumn) throws NotFoundException { if (rowIndicatorColumn == null) { return null; } int[] rowHeights = rowIndicatorColumn.getRowHeights(); if (rowHeights == null) { return null; } int maxRowHeight = getMax(rowHeights); int missingStartRows = 0; for (int rowHeight : rowHeights) { missingStartRows += maxRowHeight - rowHeight; if (rowHeight > 0) { break; } } Codeword[] codewords = rowIndicatorColumn.getCodewords(); for (int row = 0; missingStartRows > 0 && codewords[row] == null; row++) { missingStartRows--; } int missingEndRows = 0; for (int row = rowHeights.length - 1; row >= 0; row--) { missingEndRows += maxRowHeight - rowHeights[row]; if (rowHeights[row] > 0) { break; } } for (int row = codewords.length - 1; missingEndRows > 0 && codewords[row] == null; row--) { missingEndRows--; } return rowIndicatorColumn.getBoundingBox().addMissingRows(missingStartRows, missingEndRows, rowIndicatorColumn.isLeft()); } private static int getMax(int[] values) { int maxValue = -1; for (int value : values) { maxValue = Math.max(maxValue, value); } return maxValue; } private static BarcodeMetadata getBarcodeMetadata(DetectionResultRowIndicatorColumn leftRowIndicatorColumn, DetectionResultRowIndicatorColumn rightRowIndicatorColumn) { BarcodeMetadata leftBarcodeMetadata; if (leftRowIndicatorColumn == null || (leftBarcodeMetadata = leftRowIndicatorColumn.getBarcodeMetadata()) == null) { return rightRowIndicatorColumn == null ? null : rightRowIndicatorColumn.getBarcodeMetadata(); } BarcodeMetadata rightBarcodeMetadata; if (rightRowIndicatorColumn == null || (rightBarcodeMetadata = rightRowIndicatorColumn.getBarcodeMetadata()) == null) { return leftBarcodeMetadata; } if (leftBarcodeMetadata.getColumnCount() != rightBarcodeMetadata.getColumnCount() && leftBarcodeMetadata.getErrorCorrectionLevel() != rightBarcodeMetadata.getErrorCorrectionLevel() && leftBarcodeMetadata.getRowCount() != rightBarcodeMetadata.getRowCount()) { return null; } return leftBarcodeMetadata; } private static DetectionResultRowIndicatorColumn getRowIndicatorColumn(BitMatrix image, BoundingBox boundingBox, ResultPoint startPoint, boolean leftToRight, int minCodewordWidth, int maxCodewordWidth) { DetectionResultRowIndicatorColumn rowIndicatorColumn = new DetectionResultRowIndicatorColumn(boundingBox, leftToRight); for (int i = 0; i < 2; i++) { int increment = i == 0 ? 1 : -1; int startColumn = (int) startPoint.getX(); for (int imageRow = (int) startPoint.getY(); imageRow <= boundingBox.getMaxY() && imageRow >= boundingBox.getMinY(); imageRow += increment) { Codeword codeword = detectCodeword(image, 0, image.getWidth(), leftToRight, startColumn, imageRow, minCodewordWidth, maxCodewordWidth); if (codeword != null) { rowIndicatorColumn.setCodeword(imageRow, codeword); if (leftToRight) { startColumn = codeword.getStartX(); } else { startColumn = codeword.getEndX(); } } } } return rowIndicatorColumn; } private static void adjustCodewordCount(DetectionResult detectionResult, BarcodeValue[][] barcodeMatrix) throws NotFoundException { BarcodeValue barcodeMatrix01 = barcodeMatrix[0][1]; int[] numberOfCodewords = barcodeMatrix01.getValue(); int calculatedNumberOfCodewords = detectionResult.getBarcodeColumnCount() * detectionResult.getBarcodeRowCount() - getNumberOfECCodeWords(detectionResult.getBarcodeECLevel()); if (numberOfCodewords.length == 0) { if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > PDF417Common.MAX_CODEWORDS_IN_BARCODE) { throw NotFoundException.getNotFoundInstance(); } barcodeMatrix01.setValue(calculatedNumberOfCodewords); } else if (numberOfCodewords[0] != calculatedNumberOfCodewords) { // The calculated one is more reliable as it is derived from the row indicator columns barcodeMatrix01.setValue(calculatedNumberOfCodewords); } } private static DecoderResult createDecoderResult(DetectionResult detectionResult) throws FormatException, ChecksumException, NotFoundException { BarcodeValue[][] barcodeMatrix = createBarcodeMatrix(detectionResult); adjustCodewordCount(detectionResult, barcodeMatrix); CollectionGiven data and error-correction codewords received, possibly corrupted by errors, attempts to * correct the errors in-place.
* * @param codewords data and error correction codewords * @param erasures positions of any known erasures * @param numECCodewords number of error correction codewords that are available in codewords * @throws ChecksumException if error correction fails */ private static int correctErrors(int[] codewords, int[] erasures, int numECCodewords) throws ChecksumException { if (erasures != null && erasures.length > numECCodewords / 2 + MAX_ERRORS || numECCodewords < 0 || numECCodewords > MAX_EC_CODEWORDS) { // Too many errors or EC Codewords is corrupted throw ChecksumException.getChecksumInstance(); } return errorCorrection.decode(codewords, numECCodewords, erasures); } /** * Verify that all is OK with the codeword array. */ private static void verifyCodewordCount(int[] codewords, int numECCodewords) throws FormatException { if (codewords.length < 4) { // Codeword array size should be at least 4 allowing for // Count CW, At least one Data CW, Error Correction CW, Error Correction CW throw FormatException.getFormatInstance(); } // The first codeword, the Symbol Length Descriptor, shall always encode the total number of data // codewords in the symbol, including the Symbol Length Descriptor itself, data codewords and pad // codewords, but excluding the number of error correction codewords. int numberOfCodewords = codewords[0]; if (numberOfCodewords > codewords.length) { throw FormatException.getFormatInstance(); } if (numberOfCodewords == 0) { // Reset to the length of the array - 8 (Allow for at least level 3 Error Correction (8 Error Codewords) if (numECCodewords < codewords.length) { codewords[0] = codewords.length - numECCodewords; } else { throw FormatException.getFormatInstance(); } } } private static int[] getBitCountForCodeword(int codeword) { int[] result = new int[8]; int previousValue = 0; int i = result.length - 1; while (true) { if ((codeword & 0x1) != previousValue) { previousValue = codeword & 0x1; i--; if (i < 0) { break; } } result[i]++; codeword >>= 1; } return result; } private static int getCodewordBucketNumber(int codeword) { return getCodewordBucketNumber(getBitCountForCodeword(codeword)); } private static int getCodewordBucketNumber(int[] moduleBitCount) { return (moduleBitCount[0] - moduleBitCount[2] + moduleBitCount[4] - moduleBitCount[6] + 9) % 9; } public static String toString(BarcodeValue[][] barcodeMatrix) { try (Formatter formatter = new Formatter()) { for (int row = 0; row < barcodeMatrix.length; row++) { formatter.format("Row %2d: ", row); for (int column = 0; column < barcodeMatrix[row].length; column++) { BarcodeValue barcodeValue = barcodeMatrix[row][column]; if (barcodeValue.getValue().length == 0) { formatter.format(" ", (Object[]) null); } else { formatter.format("%4d(%2d)", barcodeValue.getValue()[0], barcodeValue.getConfidence(barcodeValue.getValue()[0])); } } formatter.format("%n"); } return formatter.toString(); } } } ================================================ FILE: scanner/src/main/java/com/google/zxing/pdf417/decoder/ec/ErrorCorrection.java ================================================ /* * Copyright 2012 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.pdf417.decoder.ec; import com.google.zxing.ChecksumException; /** *PDF417 error correction implementation.
* *This example * is quite useful in understanding the algorithm.
* * @author Sean Owen * @see com.google.zxing.common.reedsolomon.ReedSolomonDecoder */ public final class ErrorCorrection { private final ModulusGF field; public ErrorCorrection() { this.field = ModulusGF.PDF417_GF; } /** * @param received received codewords * @param numECCodewords number of those codewords used for EC * @param erasures location of erasures * @return number of errors * @throws ChecksumException if errors cannot be corrected, maybe because of too many errors */ public int decode(int[] received, int numECCodewords, int[] erasures) throws ChecksumException { ModulusPoly poly = new ModulusPoly(field, received); int[] S = new int[numECCodewords]; boolean error = false; for (int i = numECCodewords; i > 0; i--) { int eval = poly.evaluateAt(field.exp(i)); S[numECCodewords - i] = eval; if (eval != 0) { error = true; } } if (!error) { return 0; } ModulusPoly knownErrors = field.getOne(); if (erasures != null) { for (int erasure : erasures) { int b = field.exp(received.length - 1 - erasure); // Add (1 - bx) term: ModulusPoly term = new ModulusPoly(field, new int[]{field.subtract(0, b), 1}); knownErrors = knownErrors.multiply(term); } } ModulusPoly syndrome = new ModulusPoly(field, S); //syndrome = syndrome.multiply(knownErrors); ModulusPoly[] sigmaOmega = runEuclideanAlgorithm(field.buildMonomial(numECCodewords, 1), syndrome, numECCodewords); ModulusPoly sigma = sigmaOmega[0]; ModulusPoly omega = sigmaOmega[1]; //sigma = sigma.multiply(knownErrors); int[] errorLocations = findErrorLocations(sigma); int[] errorMagnitudes = findErrorMagnitudes(omega, sigma, errorLocations); for (int i = 0; i < errorLocations.length; i++) { int position = received.length - 1 - field.log(errorLocations[i]); if (position < 0) { throw ChecksumException.getChecksumInstance(); } received[position] = field.subtract(received[position], errorMagnitudes[i]); } return errorLocations.length; } private ModulusPoly[] runEuclideanAlgorithm(ModulusPoly a, ModulusPoly b, int R) throws ChecksumException { // Assume a's degree is >= b's if (a.getDegree() < b.getDegree()) { ModulusPoly temp = a; a = b; b = temp; } ModulusPoly rLast = a; ModulusPoly r = b; ModulusPoly tLast = field.getZero(); ModulusPoly t = field.getOne(); // Run Euclidean algorithm until r's degree is less than R/2 while (r.getDegree() >= R / 2) { ModulusPoly rLastLast = rLast; ModulusPoly tLastLast = tLast; rLast = r; tLast = t; // Divide rLastLast by rLast, with quotient in q and remainder in r if (rLast.isZero()) { // Oops, Euclidean algorithm already terminated? throw ChecksumException.getChecksumInstance(); } r = rLastLast; ModulusPoly q = field.getZero(); int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree()); int dltInverse = field.inverse(denominatorLeadingTerm); while (r.getDegree() >= rLast.getDegree() && !r.isZero()) { int degreeDiff = r.getDegree() - rLast.getDegree(); int scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse); q = q.add(field.buildMonomial(degreeDiff, scale)); r = r.subtract(rLast.multiplyByMonomial(degreeDiff, scale)); } t = q.multiply(tLast).subtract(tLastLast).negative(); } int sigmaTildeAtZero = t.getCoefficient(0); if (sigmaTildeAtZero == 0) { throw ChecksumException.getChecksumInstance(); } int inverse = field.inverse(sigmaTildeAtZero); ModulusPoly sigma = t.multiply(inverse); ModulusPoly omega = r.multiply(inverse); return new ModulusPoly[]{sigma, omega}; } private int[] findErrorLocations(ModulusPoly errorLocator) throws ChecksumException { // This is a direct application of Chien's search int numErrors = errorLocator.getDegree(); int[] result = new int[numErrors]; int e = 0; for (int i = 1; i < field.getSize() && e < numErrors; i++) { if (errorLocator.evaluateAt(i) == 0) { result[e] = field.inverse(i); e++; } } if (e != numErrors) { throw ChecksumException.getChecksumInstance(); } return result; } private int[] findErrorMagnitudes(ModulusPoly errorEvaluator, ModulusPoly errorLocator, int[] errorLocations) { int errorLocatorDegree = errorLocator.getDegree(); int[] formalDerivativeCoefficients = new int[errorLocatorDegree]; for (int i = 1; i <= errorLocatorDegree; i++) { formalDerivativeCoefficients[errorLocatorDegree - i] = field.multiply(i, errorLocator.getCoefficient(i)); } ModulusPoly formalDerivative = new ModulusPoly(field, formalDerivativeCoefficients); // This is directly applying Forney's Formula int s = errorLocations.length; int[] result = new int[s]; for (int i = 0; i < s; i++) { int xiInverse = field.inverse(errorLocations[i]); int numerator = field.subtract(0, errorEvaluator.evaluateAt(xiInverse)); int denominator = field.inverse(formalDerivative.evaluateAt(xiInverse)); result[i] = field.multiply(numerator, denominator); } return result; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/pdf417/decoder/ec/ModulusGF.java ================================================ /* * Copyright 2012 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.pdf417.decoder.ec; import com.google.zxing.pdf417.PDF417Common; /** *A field based on powers of a generator integer, modulo some modulus.
* * @author Sean Owen * @see com.google.zxing.common.reedsolomon.GenericGF */ public final class ModulusGF { public static final ModulusGF PDF417_GF = new ModulusGF(PDF417Common.NUMBER_OF_CODEWORDS, 3); private final int[] expTable; private final int[] logTable; private final ModulusPoly zero; private final ModulusPoly one; private final int modulus; private ModulusGF(int modulus, int generator) { this.modulus = modulus; expTable = new int[modulus]; logTable = new int[modulus]; int x = 1; for (int i = 0; i < modulus; i++) { expTable[i] = x; x = (x * generator) % modulus; } for (int i = 0; i < modulus - 1; i++) { logTable[expTable[i]] = i; } // logTable[0] == 0 but this should never be used zero = new ModulusPoly(this, new int[]{0}); one = new ModulusPoly(this, new int[]{1}); } ModulusPoly getZero() { return zero; } ModulusPoly getOne() { return one; } ModulusPoly buildMonomial(int degree, int coefficient) { if (degree < 0) { throw new IllegalArgumentException(); } if (coefficient == 0) { return zero; } int[] coefficients = new int[degree + 1]; coefficients[0] = coefficient; return new ModulusPoly(this, coefficients); } int add(int a, int b) { return (a + b) % modulus; } int subtract(int a, int b) { return (modulus + a - b) % modulus; } int exp(int a) { return expTable[a]; } int log(int a) { if (a == 0) { throw new IllegalArgumentException(); } return logTable[a]; } int inverse(int a) { if (a == 0) { throw new ArithmeticException(); } return expTable[modulus - logTable[a] - 1]; } int multiply(int a, int b) { if (a == 0 || b == 0) { return 0; } return expTable[(logTable[a] + logTable[b]) % (modulus - 1)]; } int getSize() { return modulus; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/pdf417/decoder/ec/ModulusPoly.java ================================================ /* * Copyright 2012 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.pdf417.decoder.ec; /** * @author Sean Owen * @see com.google.zxing.common.reedsolomon.GenericGFPoly */ final class ModulusPoly { private final ModulusGF field; private final int[] coefficients; ModulusPoly(ModulusGF field, int[] coefficients) { if (coefficients.length == 0) { throw new IllegalArgumentException(); } this.field = field; int coefficientsLength = coefficients.length; if (coefficientsLength > 1 && coefficients[0] == 0) { // Leading term must be non-zero for anything except the constant polynomial "0" int firstNonZero = 1; while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) { firstNonZero++; } if (firstNonZero == coefficientsLength) { this.coefficients = new int[]{0}; } else { this.coefficients = new int[coefficientsLength - firstNonZero]; System.arraycopy(coefficients, firstNonZero, this.coefficients, 0, this.coefficients.length); } } else { this.coefficients = coefficients; } } int[] getCoefficients() { return coefficients; } /** * @return degree of this polynomial */ int getDegree() { return coefficients.length - 1; } /** * @return true iff this polynomial is the monomial "0" */ boolean isZero() { return coefficients[0] == 0; } /** * @return coefficient of x^degree term in this polynomial */ int getCoefficient(int degree) { return coefficients[coefficients.length - 1 - degree]; } /** * @return evaluation of this polynomial at a given point */ int evaluateAt(int a) { if (a == 0) { // Just return the x^0 coefficient return getCoefficient(0); } if (a == 1) { // Just the sum of the coefficients int result = 0; for (int coefficient : coefficients) { result = field.add(result, coefficient); } return result; } int result = coefficients[0]; int size = coefficients.length; for (int i = 1; i < size; i++) { result = field.add(field.multiply(a, result), coefficients[i]); } return result; } ModulusPoly add(ModulusPoly other) { if (!field.equals(other.field)) { throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field"); } if (isZero()) { return other; } if (other.isZero()) { return this; } int[] smallerCoefficients = this.coefficients; int[] largerCoefficients = other.coefficients; if (smallerCoefficients.length > largerCoefficients.length) { int[] temp = smallerCoefficients; smallerCoefficients = largerCoefficients; largerCoefficients = temp; } int[] sumDiff = new int[largerCoefficients.length]; int lengthDiff = largerCoefficients.length - smallerCoefficients.length; // Copy high-order terms only found in higher-degree polynomial's coefficients System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff); for (int i = lengthDiff; i < largerCoefficients.length; i++) { sumDiff[i] = field.add(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); } return new ModulusPoly(field, sumDiff); } ModulusPoly subtract(ModulusPoly other) { if (!field.equals(other.field)) { throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field"); } if (other.isZero()) { return this; } return add(other.negative()); } ModulusPoly multiply(ModulusPoly other) { if (!field.equals(other.field)) { throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field"); } if (isZero() || other.isZero()) { return field.getZero(); } int[] aCoefficients = this.coefficients; int aLength = aCoefficients.length; int[] bCoefficients = other.coefficients; int bLength = bCoefficients.length; int[] product = new int[aLength + bLength - 1]; for (int i = 0; i < aLength; i++) { int aCoeff = aCoefficients[i]; for (int j = 0; j < bLength; j++) { product[i + j] = field.add(product[i + j], field.multiply(aCoeff, bCoefficients[j])); } } return new ModulusPoly(field, product); } ModulusPoly negative() { int size = coefficients.length; int[] negativeCoefficients = new int[size]; for (int i = 0; i < size; i++) { negativeCoefficients[i] = field.subtract(0, coefficients[i]); } return new ModulusPoly(field, negativeCoefficients); } ModulusPoly multiply(int scalar) { if (scalar == 0) { return field.getZero(); } if (scalar == 1) { return this; } int size = coefficients.length; int[] product = new int[size]; for (int i = 0; i < size; i++) { product[i] = field.multiply(coefficients[i], scalar); } return new ModulusPoly(field, product); } ModulusPoly multiplyByMonomial(int degree, int coefficient) { if (degree < 0) { throw new IllegalArgumentException(); } if (coefficient == 0) { return field.getZero(); } int size = coefficients.length; int[] product = new int[size + degree]; for (int i = 0; i < size; i++) { product[i] = field.multiply(coefficients[i], coefficient); } return new ModulusPoly(field, product); } /* ModulusPoly[] divide(ModulusPoly other) { if (!field.equals(other.field)) { throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field"); } if (other.isZero()) { throw new IllegalArgumentException("Divide by 0"); } ModulusPoly quotient = field.getZero(); ModulusPoly remainder = this; int denominatorLeadingTerm = other.getCoefficient(other.getDegree()); int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm); while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) { int degreeDifference = remainder.getDegree() - other.getDegree(); int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm); ModulusPoly term = other.multiplyByMonomial(degreeDifference, scale); ModulusPoly iterationQuotient = field.buildMonomial(degreeDifference, scale); quotient = quotient.add(iterationQuotient); remainder = remainder.subtract(term); } return new ModulusPoly[] { quotient, remainder }; } */ @Override public String toString() { StringBuilder result = new StringBuilder(8 * getDegree()); for (int degree = getDegree(); degree >= 0; degree--) { int coefficient = getCoefficient(degree); if (coefficient != 0) { if (coefficient < 0) { result.append(" - "); coefficient = -coefficient; } else { if (result.length() > 0) { result.append(" + "); } } if (degree == 0 || coefficient != 1) { result.append(coefficient); } if (degree != 0) { if (degree == 1) { result.append('x'); } else { result.append("x^"); result.append(degree); } } } } return result.toString(); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/pdf417/detector/Detector.java ================================================ /* * Copyright 2009 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.pdf417.detector; import com.google.zxing.BinaryBitmap; import com.google.zxing.DecodeHintType; import com.google.zxing.NotFoundException; import com.google.zxing.ResultPoint; import com.google.zxing.common.BitMatrix; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; /** *Encapsulates logic that can detect a PDF417 Code in an image, even if the * PDF417 Code is rotated or skewed, or partially obscured.
* * @author SITA Lab (kevin.osullivan@sita.aero) * @author dswitkin@google.com (Daniel Switkin) * @author Guenther Grau */ public final class Detector { private static final int[] INDEXES_START_PATTERN = {0, 4, 1, 5}; private static final int[] INDEXES_STOP_PATTERN = {6, 2, 7, 3}; private static final float MAX_AVG_VARIANCE = 0.42f; private static final float MAX_INDIVIDUAL_VARIANCE = 0.8f; // B S B S B S B S Bar/Space pattern // 11111111 0 1 0 1 0 1 000 private static final int[] START_PATTERN = {8, 1, 1, 1, 1, 1, 1, 3}; // 1111111 0 1 000 1 0 1 00 1 private static final int[] STOP_PATTERN = {7, 1, 1, 3, 1, 1, 1, 2, 1}; private static final int MAX_PIXEL_DRIFT = 3; private static final int MAX_PATTERN_DRIFT = 5; // if we set the value too low, then we don't detect the correct height of the bar if the start patterns are damaged. // if we set the value too high, then we might detect the start pattern from a neighbor barcode. private static final int SKIPPED_ROW_COUNT_MAX = 25; // A PDF471 barcode should have at least 3 rows, with each row being >= 3 times the module width. Therefore it should be at least // 9 pixels tall. To be conservative, we use about half the size to ensure we don't miss it. private static final int ROW_STEP = 5; private static final int BARCODE_MIN_HEIGHT = 10; private Detector() { } /** *Detects a PDF417 Code in an image. Only checks 0 and 180 degree rotations.
* * @param image barcode image to decode * @param hints optional hints to detector * @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will * be found and returned * @return {@link PDF417DetectorResult} encapsulating results of detecting a PDF417 code * @throws NotFoundException if no PDF417 Code can be found */ public static PDF417DetectorResult detect(BinaryBitmap image, MapReads format information from one of its two locations within the QR Code.
* * @return {@link FormatInformation} encapsulating the QR Code's format info * @throws FormatException if both format information locations cannot be parsed as * the valid encoding of format information */ FormatInformation readFormatInformation() throws FormatException { if (parsedFormatInfo != null) { return parsedFormatInfo; } // Read top-left format info bits int formatInfoBits1 = 0; for (int i = 0; i < 6; i++) { formatInfoBits1 = copyBit(i, 8, formatInfoBits1); } // .. and skip a bit in the timing pattern ... formatInfoBits1 = copyBit(7, 8, formatInfoBits1); formatInfoBits1 = copyBit(8, 8, formatInfoBits1); formatInfoBits1 = copyBit(8, 7, formatInfoBits1); // .. and skip a bit in the timing pattern ... for (int j = 5; j >= 0; j--) { formatInfoBits1 = copyBit(8, j, formatInfoBits1); } // Read the top-right/bottom-left pattern too int dimension = bitMatrix.getHeight(); int formatInfoBits2 = 0; int jMin = dimension - 7; for (int j = dimension - 1; j >= jMin; j--) { formatInfoBits2 = copyBit(8, j, formatInfoBits2); } for (int i = dimension - 8; i < dimension; i++) { formatInfoBits2 = copyBit(i, 8, formatInfoBits2); } parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits1, formatInfoBits2); if (parsedFormatInfo != null) { return parsedFormatInfo; } throw FormatException.getFormatInstance(); } /** *Reads version information from one of its two locations within the QR Code.
* * @return {@link Version} encapsulating the QR Code's version * @throws FormatException if both version information locations cannot be parsed as * the valid encoding of version information */ Version readVersion() throws FormatException { if (parsedVersion != null) { return parsedVersion; } int dimension = bitMatrix.getHeight(); int provisionalVersion = (dimension - 17) / 4; if (provisionalVersion <= 6) { return Version.getVersionForNumber(provisionalVersion); } // Read top-right version info: 3 wide by 6 tall int versionBits = 0; int ijMin = dimension - 11; for (int j = 5; j >= 0; j--) { for (int i = dimension - 9; i >= ijMin; i--) { versionBits = copyBit(i, j, versionBits); } } Version theParsedVersion = Version.decodeVersionInformation(versionBits); if (theParsedVersion != null && theParsedVersion.getDimensionForVersion() == dimension) { parsedVersion = theParsedVersion; return theParsedVersion; } // Hmm, failed. Try bottom left: 6 wide by 3 tall versionBits = 0; for (int i = 5; i >= 0; i--) { for (int j = dimension - 9; j >= ijMin; j--) { versionBits = copyBit(i, j, versionBits); } } theParsedVersion = Version.decodeVersionInformation(versionBits); if (theParsedVersion != null && theParsedVersion.getDimensionForVersion() == dimension) { parsedVersion = theParsedVersion; return theParsedVersion; } throw FormatException.getFormatInstance(); } private int copyBit(int i, int j, int versionBits) { boolean bit = mirror ? bitMatrix.get(j, i) : bitMatrix.get(i, j); return bit ? (versionBits << 1) | 0x1 : versionBits << 1; } /** *Reads the bits in the {@link BitMatrix} representing the finder pattern in the * correct order in order to reconstruct the codewords bytes contained within the * QR Code.
* * @return bytes encoded within the QR Code * @throws FormatException if the exact number of bytes expected is not read */ byte[] readCodewords() throws FormatException { FormatInformation formatInfo = readFormatInformation(); Version version = readVersion(); // Get the data mask for the format used in this QR Code. This will exclude // some bits from reading as we wind through the bit matrix. DataMask dataMask = DataMask.values()[formatInfo.getDataMask()]; int dimension = bitMatrix.getHeight(); dataMask.unmaskBitMatrix(bitMatrix, dimension); BitMatrix functionPattern = version.buildFunctionPattern(); boolean readingUp = true; byte[] result = new byte[version.getTotalCodewords()]; int resultOffset = 0; int currentByte = 0; int bitsRead = 0; // Read columns in pairs, from right to left for (int j = dimension - 1; j > 0; j -= 2) { if (j == 6) { // Skip whole column with vertical alignment pattern; // saves time and makes the other code proceed more cleanly j--; } // Read alternatingly from bottom to top then top to bottom for (int count = 0; count < dimension; count++) { int i = readingUp ? dimension - 1 - count : count; for (int col = 0; col < 2; col++) { // Ignore bits covered by the function pattern if (!functionPattern.get(j - col, i)) { // Read a bit bitsRead++; currentByte <<= 1; if (bitMatrix.get(j - col, i)) { currentByte |= 1; } // If we've made a whole byte, save it off if (bitsRead == 8) { result[resultOffset++] = (byte) currentByte; bitsRead = 0; currentByte = 0; } } } } readingUp ^= true; // readingUp = !readingUp; // switch directions } if (resultOffset != version.getTotalCodewords()) { throw FormatException.getFormatInstance(); } return result; } /** * Revert the mask removal done while reading the code words. The bit matrix should revert to its original state. */ void remask() { if (parsedFormatInfo == null) { return; // We have no format information, and have no data mask } DataMask dataMask = DataMask.values()[parsedFormatInfo.getDataMask()]; int dimension = bitMatrix.getHeight(); dataMask.unmaskBitMatrix(bitMatrix, dimension); } /** * Prepare the parser for a mirrored operation. * This flag has effect only on the {@link #readFormatInformation()} and the * {@link #readVersion()}. Before proceeding with {@link #readCodewords()} the * {@link #mirror()} method should be called. * * @param mirror Whether to read version and format information mirrored. */ void setMirror(boolean mirror) { parsedVersion = null; parsedFormatInfo = null; this.mirror = mirror; } /** Mirror the bit matrix in order to attempt a second reading. */ void mirror() { for (int x = 0; x < bitMatrix.getWidth(); x++) { for (int y = x + 1; y < bitMatrix.getHeight(); y++) { if (bitMatrix.get(x, y) != bitMatrix.get(y, x)) { bitMatrix.flip(y, x); bitMatrix.flip(x, y); } } } } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/decoder/DataBlock.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.decoder; /** *Encapsulates a block of data within a QR Code. QR Codes may split their data into * multiple blocks, each of which is a unit of data and error-correction codewords. Each * is represented by an instance of this class.
* * @author Sean Owen */ final class DataBlock { private final int numDataCodewords; private final byte[] codewords; private DataBlock(int numDataCodewords, byte[] codewords) { this.numDataCodewords = numDataCodewords; this.codewords = codewords; } /** *When QR Codes use multiple data blocks, they are actually interleaved. * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This * method will separate the data into original blocks.
* * @param rawCodewords bytes as read directly from the QR Code * @param version version of the QR Code * @param ecLevel error-correction level of the QR Code * @return DataBlocks containing original bytes, "de-interleaved" from representation in the * QR Code */ static DataBlock[] getDataBlocks(byte[] rawCodewords, Version version, ErrorCorrectionLevel ecLevel) { if (rawCodewords.length != version.getTotalCodewords()) { throw new IllegalArgumentException(); } // Figure out the number and size of data blocks used by this version and // error correction level Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel); // First count the total number of data blocks int totalBlocks = 0; Version.ECB[] ecBlockArray = ecBlocks.getECBlocks(); for (Version.ECB ecBlock : ecBlockArray) { totalBlocks += ecBlock.getCount(); } // Now establish DataBlocks of the appropriate size and number of data codewords DataBlock[] result = new DataBlock[totalBlocks]; int numResultBlocks = 0; for (Version.ECB ecBlock : ecBlockArray) { for (int i = 0; i < ecBlock.getCount(); i++) { int numDataCodewords = ecBlock.getDataCodewords(); int numBlockCodewords = ecBlocks.getECCodewordsPerBlock() + numDataCodewords; result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]); } } // All blocks have the same amount of data, except that the last n // (where n may be 0) have 1 more byte. Figure out where these start. int shorterBlocksTotalCodewords = result[0].codewords.length; int longerBlocksStartAt = result.length - 1; while (longerBlocksStartAt >= 0) { int numCodewords = result[longerBlocksStartAt].codewords.length; if (numCodewords == shorterBlocksTotalCodewords) { break; } longerBlocksStartAt--; } longerBlocksStartAt++; int shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getECCodewordsPerBlock(); // The last elements of result may be 1 element longer; // first fill out as many elements as all of them have int rawCodewordsOffset = 0; for (int i = 0; i < shorterBlocksNumDataCodewords; i++) { for (int j = 0; j < numResultBlocks; j++) { result[j].codewords[i] = rawCodewords[rawCodewordsOffset++]; } } // Fill out the last data block in the longer ones for (int j = longerBlocksStartAt; j < numResultBlocks; j++) { result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++]; } // Now add in error correction blocks int max = result[0].codewords.length; for (int i = shorterBlocksNumDataCodewords; i < max; i++) { for (int j = 0; j < numResultBlocks; j++) { int iOffset = j < longerBlocksStartAt ? i : i + 1; result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; } } return result; } int getNumDataCodewords() { return numDataCodewords; } byte[] getCodewords() { return codewords; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.decoder; import com.google.zxing.common.BitMatrix; /** *Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations * of this class can un-mask a raw BitMatrix. For simplicity, they will unmask the entire BitMatrix, * including areas used for finder patterns, timing patterns, etc. These areas should be unused * after the point they are unmasked anyway.
* *Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position * and j is row position. In fact, as the text says, i is row position and j is column position.
* * @author Sean Owen */ enum DataMask { // See ISO 18004:2006 6.8.1 /** * 000: mask bits for which (x + y) mod 2 == 0 */ DATA_MASK_000() { @Override boolean isMasked(int i, int j) { return ((i + j) & 0x01) == 0; } }, /** * 001: mask bits for which x mod 2 == 0 */ DATA_MASK_001() { @Override boolean isMasked(int i, int j) { return (i & 0x01) == 0; } }, /** * 010: mask bits for which y mod 3 == 0 */ DATA_MASK_010() { @Override boolean isMasked(int i, int j) { return j % 3 == 0; } }, /** * 011: mask bits for which (x + y) mod 3 == 0 */ DATA_MASK_011() { @Override boolean isMasked(int i, int j) { return (i + j) % 3 == 0; } }, /** * 100: mask bits for which (x/2 + y/3) mod 2 == 0 */ DATA_MASK_100() { @Override boolean isMasked(int i, int j) { return (((i / 2) + (j / 3)) & 0x01) == 0; } }, /** * 101: mask bits for which xy mod 2 + xy mod 3 == 0 * equivalently, such that xy mod 6 == 0 */ DATA_MASK_101() { @Override boolean isMasked(int i, int j) { return (i * j) % 6 == 0; } }, /** * 110: mask bits for which (xy mod 2 + xy mod 3) mod 2 == 0 * equivalently, such that xy mod 6 < 3 */ DATA_MASK_110() { @Override boolean isMasked(int i, int j) { return ((i * j) % 6) < 3; } }, /** * 111: mask bits for which ((x+y)mod 2 + xy mod 3) mod 2 == 0 * equivalently, such that (x + y + xy mod 3) mod 2 == 0 */ DATA_MASK_111() { @Override boolean isMasked(int i, int j) { return ((i + j + ((i * j) % 3)) & 0x01) == 0; } }; // End of enum constants. /** *Implementations of this method reverse the data masking process applied to a QR Code and * make its bits ready to read.
* * @param bits representation of QR Code bits * @param dimension dimension of QR Code, represented by bits, being unmasked */ final void unmaskBitMatrix(BitMatrix bits, int dimension) { for (int i = 0; i < dimension; i++) { for (int j = 0; j < dimension; j++) { if (isMasked(i, j)) { bits.flip(j, i); } } } } abstract boolean isMasked(int i, int j); } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.decoder; import com.google.zxing.DecodeHintType; import com.google.zxing.FormatException; import com.google.zxing.common.BitSource; import com.google.zxing.common.CharacterSetECI; import com.google.zxing.common.DecoderResult; import com.google.zxing.common.StringUtils; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; /** *QR Codes can encode text as bits in one of several modes, and can use multiple modes * in one QR Code. This class decodes the bits back into text.
* *See ISO 18004:2006, 6.4.3 - 6.4.7
* * @author Sean Owen */ final class DecodedBitStreamParser { /** * See ISO 18004:2006, 6.4.4 Table 5 */ private static final char[] ALPHANUMERIC_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".toCharArray(); private static final int GB2312_SUBSET = 1; private DecodedBitStreamParser() { } static DecoderResult decode(byte[] bytes, Version version, ErrorCorrectionLevel ecLevel, MapThe main class which implements QR Code decoding -- as opposed to locating and extracting * the QR Code from an image.
* * @author Sean Owen */ public final class Decoder { private final ReedSolomonDecoder rsDecoder; public Decoder() { rsDecoder = new ReedSolomonDecoder(GenericGF.QR_CODE_FIELD_256); } public DecoderResult decode(boolean[][] image) throws ChecksumException, FormatException { return decode(image, null); } /** *Convenience method that can decode a QR Code represented as a 2D array of booleans. * "true" is taken to mean a black module.
* * @param image booleans representing white/black QR Code modules * @param hints decoding hints that should be used to influence decoding * @return text and bytes encoded within the QR Code * @throws FormatException if the QR Code cannot be decoded * @throws ChecksumException if error correction fails */ public DecoderResult decode(boolean[][] image, MapDecodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.
* * @param bits booleans representing white/black QR Code modules * @param hints decoding hints that should be used to influence decoding * @return text and bytes encoded within the QR Code * @throws FormatException if the QR Code cannot be decoded * @throws ChecksumException if error correction fails */ public DecoderResult decode(BitMatrix bits, MapGiven data and error-correction codewords received, possibly corrupted by errors, attempts to * correct the errors in-place using Reed-Solomon error correction.
* * @param codewordBytes data and error correction codewords * @param numDataCodewords number of codewords that are data bytes * @throws ChecksumException if error correction fails */ private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException { int numCodewords = codewordBytes.length; // First read into an array of ints int[] codewordsInts = new int[numCodewords]; for (int i = 0; i < numCodewords; i++) { codewordsInts[i] = codewordBytes[i] & 0xFF; } try { rsDecoder.decode(codewordsInts, codewordBytes.length - numDataCodewords); } catch (ReedSolomonException ignored) { throw ChecksumException.getChecksumInstance(); } // Copy back into array of bytes -- only need to worry about the bytes that were data // We don't care about errors in the error-correction codewords for (int i = 0; i < numDataCodewords; i++) { codewordBytes[i] = (byte) codewordsInts[i]; } } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.decoder; /** *See ISO 18004:2006, 6.5.1. This enum encapsulates the four error correction levels * defined by the QR code standard.
* * @author Sean Owen */ public enum ErrorCorrectionLevel { /** L = ~7% correction */ L(0x01), /** M = ~15% correction */ M(0x00), /** Q = ~25% correction */ Q(0x03), /** H = ~30% correction */ H(0x02); private static final ErrorCorrectionLevel[] FOR_BITS = {M, L, H, Q}; private final int bits; ErrorCorrectionLevel(int bits) { this.bits = bits; } public int getBits() { return bits; } /** * @param bits int containing the two bits encoding a QR Code's error correction level * @return ErrorCorrectionLevel representing the encoded error correction level */ public static ErrorCorrectionLevel forBits(int bits) { if (bits < 0 || bits >= FOR_BITS.length) { throw new IllegalArgumentException(); } return FOR_BITS[bits]; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/decoder/FormatInformation.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.decoder; /** *Encapsulates a QR Code's format information, including the data mask used and * error correction level.
* * @author Sean Owen * @see DataMask * @see ErrorCorrectionLevel */ final class FormatInformation { private static final int FORMAT_INFO_MASK_QR = 0x5412; /** * See ISO 18004:2006, Annex C, Table C.1 */ private static final int[][] FORMAT_INFO_DECODE_LOOKUP = { {0x5412, 0x00}, {0x5125, 0x01}, {0x5E7C, 0x02}, {0x5B4B, 0x03}, {0x45F9, 0x04}, {0x40CE, 0x05}, {0x4F97, 0x06}, {0x4AA0, 0x07}, {0x77C4, 0x08}, {0x72F3, 0x09}, {0x7DAA, 0x0A}, {0x789D, 0x0B}, {0x662F, 0x0C}, {0x6318, 0x0D}, {0x6C41, 0x0E}, {0x6976, 0x0F}, {0x1689, 0x10}, {0x13BE, 0x11}, {0x1CE7, 0x12}, {0x19D0, 0x13}, {0x0762, 0x14}, {0x0255, 0x15}, {0x0D0C, 0x16}, {0x083B, 0x17}, {0x355F, 0x18}, {0x3068, 0x19}, {0x3F31, 0x1A}, {0x3A06, 0x1B}, {0x24B4, 0x1C}, {0x2183, 0x1D}, {0x2EDA, 0x1E}, {0x2BED, 0x1F}, }; private final ErrorCorrectionLevel errorCorrectionLevel; private final byte dataMask; private FormatInformation(int formatInfo) { // Bits 3,4 errorCorrectionLevel = ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03); // Bottom 3 bits dataMask = (byte) (formatInfo & 0x07); } static int numBitsDiffering(int a, int b) { return Integer.bitCount(a ^ b); } /** * @param maskedFormatInfo1 format info indicator, with mask still applied * @param maskedFormatInfo2 second copy of same info; both are checked at the same time * to establish best match * @return information about the format it specifies, or {@code null} * if doesn't seem to match any known pattern */ static FormatInformation decodeFormatInformation(int maskedFormatInfo1, int maskedFormatInfo2) { FormatInformation formatInfo = doDecodeFormatInformation(maskedFormatInfo1, maskedFormatInfo2); if (formatInfo != null) { return formatInfo; } // Should return null, but, some QR codes apparently // do not mask this info. Try again by actually masking the pattern // first return doDecodeFormatInformation(maskedFormatInfo1 ^ FORMAT_INFO_MASK_QR, maskedFormatInfo2 ^ FORMAT_INFO_MASK_QR); } private static FormatInformation doDecodeFormatInformation(int maskedFormatInfo1, int maskedFormatInfo2) { // Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing int bestDifference = Integer.MAX_VALUE; int bestFormatInfo = 0; for (int[] decodeInfo : FORMAT_INFO_DECODE_LOOKUP) { int targetInfo = decodeInfo[0]; if (targetInfo == maskedFormatInfo1 || targetInfo == maskedFormatInfo2) { // Found an exact match return new FormatInformation(decodeInfo[1]); } int bitsDifference = numBitsDiffering(maskedFormatInfo1, targetInfo); if (bitsDifference < bestDifference) { bestFormatInfo = decodeInfo[1]; bestDifference = bitsDifference; } if (maskedFormatInfo1 != maskedFormatInfo2) { // also try the other option bitsDifference = numBitsDiffering(maskedFormatInfo2, targetInfo); if (bitsDifference < bestDifference) { bestFormatInfo = decodeInfo[1]; bestDifference = bitsDifference; } } } // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits // differing means we found a match if (bestDifference <= 3) { return new FormatInformation(bestFormatInfo); } return null; } ErrorCorrectionLevel getErrorCorrectionLevel() { return errorCorrectionLevel; } byte getDataMask() { return dataMask; } @Override public int hashCode() { return (errorCorrectionLevel.ordinal() << 3) | dataMask; } @Override public boolean equals(Object o) { if (!(o instanceof FormatInformation)) { return false; } FormatInformation other = (FormatInformation) o; return this.errorCorrectionLevel == other.errorCorrectionLevel && this.dataMask == other.dataMask; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/decoder/Mode.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.decoder; /** *See ISO 18004:2006, 6.4.1, Tables 2 and 3. This enum encapsulates the various modes in which * data can be encoded to bits in the QR code standard.
* * @author Sean Owen */ public enum Mode { TERMINATOR(new int[]{0, 0, 0}, 0x00), // Not really a mode... NUMERIC(new int[]{10, 12, 14}, 0x01), ALPHANUMERIC(new int[]{9, 11, 13}, 0x02), STRUCTURED_APPEND(new int[]{0, 0, 0}, 0x03), // Not supported BYTE(new int[]{8, 16, 16}, 0x04), ECI(new int[]{0, 0, 0}, 0x07), // character counts don't apply KANJI(new int[]{8, 10, 12}, 0x08), FNC1_FIRST_POSITION(new int[]{0, 0, 0}, 0x05), FNC1_SECOND_POSITION(new int[]{0, 0, 0}, 0x09), /** See GBT 18284-2000; "Hanzi" is a transliteration of this mode name. */ HANZI(new int[]{8, 10, 12}, 0x0D); private final int[] characterCountBitsForVersions; private final int bits; Mode(int[] characterCountBitsForVersions, int bits) { this.characterCountBitsForVersions = characterCountBitsForVersions; this.bits = bits; } /** * @param bits four bits encoding a QR Code data mode * @return Mode encoded by these bits * @throws IllegalArgumentException if bits do not correspond to a known mode */ public static Mode forBits(int bits) { switch (bits) { case 0x0: return TERMINATOR; case 0x1: return NUMERIC; case 0x2: return ALPHANUMERIC; case 0x3: return STRUCTURED_APPEND; case 0x4: return BYTE; case 0x5: return FNC1_FIRST_POSITION; case 0x7: return ECI; case 0x8: return KANJI; case 0x9: return FNC1_SECOND_POSITION; case 0xD: // 0xD is defined in GBT 18284-2000, may not be supported in foreign country return HANZI; default: throw new IllegalArgumentException(); } } /** * @param version version in question * @return number of bits used, in this QR Code symbol {@link Version}, to encode the * count of characters that will follow encoded in this Mode */ public int getCharacterCountBits(Version version) { int number = version.getVersionNumber(); int offset; if (number <= 9) { offset = 0; } else if (number <= 26) { offset = 1; } else { offset = 2; } return characterCountBitsForVersions[offset]; } public int getBits() { return bits; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java ================================================ /* * Copyright 2013 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.decoder; import com.google.zxing.ResultPoint; /** * Meta-data container for QR Code decoding. Instances of this class may be used to convey information back to the * decoding caller. Callers are expected to process this. * * @see com.google.zxing.common.DecoderResult#getOther() */ public final class QRCodeDecoderMetaData { private final boolean mirrored; QRCodeDecoderMetaData(boolean mirrored) { this.mirrored = mirrored; } /** * @return true if the QR Code was mirrored. */ public boolean isMirrored() { return mirrored; } /** * Apply the result points' order correction due to mirroring. * * @param points Array of points to apply mirror correction to. */ public void applyMirroredCorrection(ResultPoint[] points) { if (!mirrored || points == null || points.length < 3) { return; } ResultPoint bottomLeft = points[0]; points[0] = points[2]; points[2] = bottomLeft; // No need to 'fix' top-left and alignment pattern. } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/decoder/Version.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.decoder; import com.google.zxing.FormatException; import com.google.zxing.common.BitMatrix; /** * See ISO 18004:2006 Annex D * * @author Sean Owen */ public final class Version { /** * See ISO 18004:2006 Annex D. * Element i represents the raw version bits that specify version i + 7 */ private static final int[] VERSION_DECODE_INFO = { 0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, 0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78, 0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB, 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B, 0x2542E, 0x26A64, 0x27541, 0x28C69 }; private static final Version[] VERSIONS = buildVersions(); private final int versionNumber; private final int[] alignmentPatternCenters; private final ECBlocks[] ecBlocks; private final int totalCodewords; private Version(int versionNumber, int[] alignmentPatternCenters, ECBlocks... ecBlocks) { this.versionNumber = versionNumber; this.alignmentPatternCenters = alignmentPatternCenters; this.ecBlocks = ecBlocks; int total = 0; int ecCodewords = ecBlocks[0].getECCodewordsPerBlock(); ECB[] ecbArray = ecBlocks[0].getECBlocks(); for (ECB ecBlock : ecbArray) { total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords); } this.totalCodewords = total; } public int getVersionNumber() { return versionNumber; } public int[] getAlignmentPatternCenters() { return alignmentPatternCenters; } public int getTotalCodewords() { return totalCodewords; } public int getDimensionForVersion() { return 17 + 4 * versionNumber; } public ECBlocks getECBlocksForLevel(ErrorCorrectionLevel ecLevel) { return ecBlocks[ecLevel.ordinal()]; } /** *Deduces version information purely from QR Code dimensions.
* * @param dimension dimension in modules * @return Version for a QR Code of that dimension * @throws FormatException if dimension is not 1 mod 4 */ public static Version getProvisionalVersionForDimension(int dimension) throws FormatException { if (dimension % 4 != 1) { throw FormatException.getFormatInstance(); } try { return getVersionForNumber((dimension - 17) / 4); } catch (IllegalArgumentException ignored) { throw FormatException.getFormatInstance(); } } public static Version getVersionForNumber(int versionNumber) { if (versionNumber < 1 || versionNumber > 40) { throw new IllegalArgumentException(); } return VERSIONS[versionNumber - 1]; } static Version decodeVersionInformation(int versionBits) { int bestDifference = Integer.MAX_VALUE; int bestVersion = 0; for (int i = 0; i < VERSION_DECODE_INFO.length; i++) { int targetVersion = VERSION_DECODE_INFO[i]; // Do the version info bits match exactly? done. if (targetVersion == versionBits) { return getVersionForNumber(i + 7); } // Otherwise see if this is the closest to a real version info bit string // we have seen so far int bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion); if (bitsDifference < bestDifference) { bestVersion = i + 7; bestDifference = bitsDifference; } } // We can tolerate up to 3 bits of error since no two version info codewords will // differ in less than 8 bits. if (bestDifference <= 3) { return getVersionForNumber(bestVersion); } // If we didn't find a close enough match, fail return null; } /** * See ISO 18004:2006 Annex E */ BitMatrix buildFunctionPattern() { int dimension = getDimensionForVersion(); BitMatrix bitMatrix = new BitMatrix(dimension); // Top left finder pattern + separator + format bitMatrix.setRegion(0, 0, 9, 9); // Top right finder pattern + separator + format bitMatrix.setRegion(dimension - 8, 0, 8, 9); // Bottom left finder pattern + separator + format bitMatrix.setRegion(0, dimension - 8, 9, 8); // Alignment patterns int max = alignmentPatternCenters.length; for (int x = 0; x < max; x++) { int i = alignmentPatternCenters[x] - 2; for (int y = 0; y < max; y++) { if ((x == 0 && (y == 0 || y == max - 1)) || (x == max - 1 && y == 0)) { // No alignment patterns near the three finder patterns continue; } bitMatrix.setRegion(alignmentPatternCenters[y] - 2, i, 5, 5); } } // Vertical timing pattern bitMatrix.setRegion(6, 9, 1, dimension - 17); // Horizontal timing pattern bitMatrix.setRegion(9, 6, dimension - 17, 1); if (versionNumber > 6) { // Version info, top right bitMatrix.setRegion(dimension - 11, 0, 3, 6); // Version info, bottom left bitMatrix.setRegion(0, dimension - 11, 6, 3); } return bitMatrix; } /** *Encapsulates a set of error-correction blocks in one symbol version. Most versions will * use blocks of differing sizes within one version, so, this encapsulates the parameters for * each set of blocks. It also holds the number of error-correction codewords per block since it * will be the same across all blocks within one version.
*/ public static final class ECBlocks { private final int ecCodewordsPerBlock; private final ECB[] ecBlocks; ECBlocks(int ecCodewordsPerBlock, ECB... ecBlocks) { this.ecCodewordsPerBlock = ecCodewordsPerBlock; this.ecBlocks = ecBlocks; } public int getECCodewordsPerBlock() { return ecCodewordsPerBlock; } public int getNumBlocks() { int total = 0; for (ECB ecBlock : ecBlocks) { total += ecBlock.getCount(); } return total; } public int getTotalECCodewords() { return ecCodewordsPerBlock * getNumBlocks(); } public ECB[] getECBlocks() { return ecBlocks; } } /** *Encapsulates the parameters for one error-correction block in one symbol version. * This includes the number of data codewords, and the number of times a block with these * parameters is used consecutively in the QR code version's format.
*/ public static final class ECB { private final int count; private final int dataCodewords; ECB(int count, int dataCodewords) { this.count = count; this.dataCodewords = dataCodewords; } public int getCount() { return count; } public int getDataCodewords() { return dataCodewords; } } @Override public String toString() { return String.valueOf(versionNumber); } /** * See ISO 18004:2006 6.5.1 Table 9 */ private static Version[] buildVersions() { return new Version[]{ new Version(1, new int[]{}, new ECBlocks(7, new ECB(1, 19)), new ECBlocks(10, new ECB(1, 16)), new ECBlocks(13, new ECB(1, 13)), new ECBlocks(17, new ECB(1, 9))), new Version(2, new int[]{6, 18}, new ECBlocks(10, new ECB(1, 34)), new ECBlocks(16, new ECB(1, 28)), new ECBlocks(22, new ECB(1, 22)), new ECBlocks(28, new ECB(1, 16))), new Version(3, new int[]{6, 22}, new ECBlocks(15, new ECB(1, 55)), new ECBlocks(26, new ECB(1, 44)), new ECBlocks(18, new ECB(2, 17)), new ECBlocks(22, new ECB(2, 13))), new Version(4, new int[]{6, 26}, new ECBlocks(20, new ECB(1, 80)), new ECBlocks(18, new ECB(2, 32)), new ECBlocks(26, new ECB(2, 24)), new ECBlocks(16, new ECB(4, 9))), new Version(5, new int[]{6, 30}, new ECBlocks(26, new ECB(1, 108)), new ECBlocks(24, new ECB(2, 43)), new ECBlocks(18, new ECB(2, 15), new ECB(2, 16)), new ECBlocks(22, new ECB(2, 11), new ECB(2, 12))), new Version(6, new int[]{6, 34}, new ECBlocks(18, new ECB(2, 68)), new ECBlocks(16, new ECB(4, 27)), new ECBlocks(24, new ECB(4, 19)), new ECBlocks(28, new ECB(4, 15))), new Version(7, new int[]{6, 22, 38}, new ECBlocks(20, new ECB(2, 78)), new ECBlocks(18, new ECB(4, 31)), new ECBlocks(18, new ECB(2, 14), new ECB(4, 15)), new ECBlocks(26, new ECB(4, 13), new ECB(1, 14))), new Version(8, new int[]{6, 24, 42}, new ECBlocks(24, new ECB(2, 97)), new ECBlocks(22, new ECB(2, 38), new ECB(2, 39)), new ECBlocks(22, new ECB(4, 18), new ECB(2, 19)), new ECBlocks(26, new ECB(4, 14), new ECB(2, 15))), new Version(9, new int[]{6, 26, 46}, new ECBlocks(30, new ECB(2, 116)), new ECBlocks(22, new ECB(3, 36), new ECB(2, 37)), new ECBlocks(20, new ECB(4, 16), new ECB(4, 17)), new ECBlocks(24, new ECB(4, 12), new ECB(4, 13))), new Version(10, new int[]{6, 28, 50}, new ECBlocks(18, new ECB(2, 68), new ECB(2, 69)), new ECBlocks(26, new ECB(4, 43), new ECB(1, 44)), new ECBlocks(24, new ECB(6, 19), new ECB(2, 20)), new ECBlocks(28, new ECB(6, 15), new ECB(2, 16))), new Version(11, new int[]{6, 30, 54}, new ECBlocks(20, new ECB(4, 81)), new ECBlocks(30, new ECB(1, 50), new ECB(4, 51)), new ECBlocks(28, new ECB(4, 22), new ECB(4, 23)), new ECBlocks(24, new ECB(3, 12), new ECB(8, 13))), new Version(12, new int[]{6, 32, 58}, new ECBlocks(24, new ECB(2, 92), new ECB(2, 93)), new ECBlocks(22, new ECB(6, 36), new ECB(2, 37)), new ECBlocks(26, new ECB(4, 20), new ECB(6, 21)), new ECBlocks(28, new ECB(7, 14), new ECB(4, 15))), new Version(13, new int[]{6, 34, 62}, new ECBlocks(26, new ECB(4, 107)), new ECBlocks(22, new ECB(8, 37), new ECB(1, 38)), new ECBlocks(24, new ECB(8, 20), new ECB(4, 21)), new ECBlocks(22, new ECB(12, 11), new ECB(4, 12))), new Version(14, new int[]{6, 26, 46, 66}, new ECBlocks(30, new ECB(3, 115), new ECB(1, 116)), new ECBlocks(24, new ECB(4, 40), new ECB(5, 41)), new ECBlocks(20, new ECB(11, 16), new ECB(5, 17)), new ECBlocks(24, new ECB(11, 12), new ECB(5, 13))), new Version(15, new int[]{6, 26, 48, 70}, new ECBlocks(22, new ECB(5, 87), new ECB(1, 88)), new ECBlocks(24, new ECB(5, 41), new ECB(5, 42)), new ECBlocks(30, new ECB(5, 24), new ECB(7, 25)), new ECBlocks(24, new ECB(11, 12), new ECB(7, 13))), new Version(16, new int[]{6, 26, 50, 74}, new ECBlocks(24, new ECB(5, 98), new ECB(1, 99)), new ECBlocks(28, new ECB(7, 45), new ECB(3, 46)), new ECBlocks(24, new ECB(15, 19), new ECB(2, 20)), new ECBlocks(30, new ECB(3, 15), new ECB(13, 16))), new Version(17, new int[]{6, 30, 54, 78}, new ECBlocks(28, new ECB(1, 107), new ECB(5, 108)), new ECBlocks(28, new ECB(10, 46), new ECB(1, 47)), new ECBlocks(28, new ECB(1, 22), new ECB(15, 23)), new ECBlocks(28, new ECB(2, 14), new ECB(17, 15))), new Version(18, new int[]{6, 30, 56, 82}, new ECBlocks(30, new ECB(5, 120), new ECB(1, 121)), new ECBlocks(26, new ECB(9, 43), new ECB(4, 44)), new ECBlocks(28, new ECB(17, 22), new ECB(1, 23)), new ECBlocks(28, new ECB(2, 14), new ECB(19, 15))), new Version(19, new int[]{6, 30, 58, 86}, new ECBlocks(28, new ECB(3, 113), new ECB(4, 114)), new ECBlocks(26, new ECB(3, 44), new ECB(11, 45)), new ECBlocks(26, new ECB(17, 21), new ECB(4, 22)), new ECBlocks(26, new ECB(9, 13), new ECB(16, 14))), new Version(20, new int[]{6, 34, 62, 90}, new ECBlocks(28, new ECB(3, 107), new ECB(5, 108)), new ECBlocks(26, new ECB(3, 41), new ECB(13, 42)), new ECBlocks(30, new ECB(15, 24), new ECB(5, 25)), new ECBlocks(28, new ECB(15, 15), new ECB(10, 16))), new Version(21, new int[]{6, 28, 50, 72, 94}, new ECBlocks(28, new ECB(4, 116), new ECB(4, 117)), new ECBlocks(26, new ECB(17, 42)), new ECBlocks(28, new ECB(17, 22), new ECB(6, 23)), new ECBlocks(30, new ECB(19, 16), new ECB(6, 17))), new Version(22, new int[]{6, 26, 50, 74, 98}, new ECBlocks(28, new ECB(2, 111), new ECB(7, 112)), new ECBlocks(28, new ECB(17, 46)), new ECBlocks(30, new ECB(7, 24), new ECB(16, 25)), new ECBlocks(24, new ECB(34, 13))), new Version(23, new int[]{6, 30, 54, 78, 102}, new ECBlocks(30, new ECB(4, 121), new ECB(5, 122)), new ECBlocks(28, new ECB(4, 47), new ECB(14, 48)), new ECBlocks(30, new ECB(11, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(16, 15), new ECB(14, 16))), new Version(24, new int[]{6, 28, 54, 80, 106}, new ECBlocks(30, new ECB(6, 117), new ECB(4, 118)), new ECBlocks(28, new ECB(6, 45), new ECB(14, 46)), new ECBlocks(30, new ECB(11, 24), new ECB(16, 25)), new ECBlocks(30, new ECB(30, 16), new ECB(2, 17))), new Version(25, new int[]{6, 32, 58, 84, 110}, new ECBlocks(26, new ECB(8, 106), new ECB(4, 107)), new ECBlocks(28, new ECB(8, 47), new ECB(13, 48)), new ECBlocks(30, new ECB(7, 24), new ECB(22, 25)), new ECBlocks(30, new ECB(22, 15), new ECB(13, 16))), new Version(26, new int[]{6, 30, 58, 86, 114}, new ECBlocks(28, new ECB(10, 114), new ECB(2, 115)), new ECBlocks(28, new ECB(19, 46), new ECB(4, 47)), new ECBlocks(28, new ECB(28, 22), new ECB(6, 23)), new ECBlocks(30, new ECB(33, 16), new ECB(4, 17))), new Version(27, new int[]{6, 34, 62, 90, 118}, new ECBlocks(30, new ECB(8, 122), new ECB(4, 123)), new ECBlocks(28, new ECB(22, 45), new ECB(3, 46)), new ECBlocks(30, new ECB(8, 23), new ECB(26, 24)), new ECBlocks(30, new ECB(12, 15), new ECB(28, 16))), new Version(28, new int[]{6, 26, 50, 74, 98, 122}, new ECBlocks(30, new ECB(3, 117), new ECB(10, 118)), new ECBlocks(28, new ECB(3, 45), new ECB(23, 46)), new ECBlocks(30, new ECB(4, 24), new ECB(31, 25)), new ECBlocks(30, new ECB(11, 15), new ECB(31, 16))), new Version(29, new int[]{6, 30, 54, 78, 102, 126}, new ECBlocks(30, new ECB(7, 116), new ECB(7, 117)), new ECBlocks(28, new ECB(21, 45), new ECB(7, 46)), new ECBlocks(30, new ECB(1, 23), new ECB(37, 24)), new ECBlocks(30, new ECB(19, 15), new ECB(26, 16))), new Version(30, new int[]{6, 26, 52, 78, 104, 130}, new ECBlocks(30, new ECB(5, 115), new ECB(10, 116)), new ECBlocks(28, new ECB(19, 47), new ECB(10, 48)), new ECBlocks(30, new ECB(15, 24), new ECB(25, 25)), new ECBlocks(30, new ECB(23, 15), new ECB(25, 16))), new Version(31, new int[]{6, 30, 56, 82, 108, 134}, new ECBlocks(30, new ECB(13, 115), new ECB(3, 116)), new ECBlocks(28, new ECB(2, 46), new ECB(29, 47)), new ECBlocks(30, new ECB(42, 24), new ECB(1, 25)), new ECBlocks(30, new ECB(23, 15), new ECB(28, 16))), new Version(32, new int[]{6, 34, 60, 86, 112, 138}, new ECBlocks(30, new ECB(17, 115)), new ECBlocks(28, new ECB(10, 46), new ECB(23, 47)), new ECBlocks(30, new ECB(10, 24), new ECB(35, 25)), new ECBlocks(30, new ECB(19, 15), new ECB(35, 16))), new Version(33, new int[]{6, 30, 58, 86, 114, 142}, new ECBlocks(30, new ECB(17, 115), new ECB(1, 116)), new ECBlocks(28, new ECB(14, 46), new ECB(21, 47)), new ECBlocks(30, new ECB(29, 24), new ECB(19, 25)), new ECBlocks(30, new ECB(11, 15), new ECB(46, 16))), new Version(34, new int[]{6, 34, 62, 90, 118, 146}, new ECBlocks(30, new ECB(13, 115), new ECB(6, 116)), new ECBlocks(28, new ECB(14, 46), new ECB(23, 47)), new ECBlocks(30, new ECB(44, 24), new ECB(7, 25)), new ECBlocks(30, new ECB(59, 16), new ECB(1, 17))), new Version(35, new int[]{6, 30, 54, 78, 102, 126, 150}, new ECBlocks(30, new ECB(12, 121), new ECB(7, 122)), new ECBlocks(28, new ECB(12, 47), new ECB(26, 48)), new ECBlocks(30, new ECB(39, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(22, 15), new ECB(41, 16))), new Version(36, new int[]{6, 24, 50, 76, 102, 128, 154}, new ECBlocks(30, new ECB(6, 121), new ECB(14, 122)), new ECBlocks(28, new ECB(6, 47), new ECB(34, 48)), new ECBlocks(30, new ECB(46, 24), new ECB(10, 25)), new ECBlocks(30, new ECB(2, 15), new ECB(64, 16))), new Version(37, new int[]{6, 28, 54, 80, 106, 132, 158}, new ECBlocks(30, new ECB(17, 122), new ECB(4, 123)), new ECBlocks(28, new ECB(29, 46), new ECB(14, 47)), new ECBlocks(30, new ECB(49, 24), new ECB(10, 25)), new ECBlocks(30, new ECB(24, 15), new ECB(46, 16))), new Version(38, new int[]{6, 32, 58, 84, 110, 136, 162}, new ECBlocks(30, new ECB(4, 122), new ECB(18, 123)), new ECBlocks(28, new ECB(13, 46), new ECB(32, 47)), new ECBlocks(30, new ECB(48, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(42, 15), new ECB(32, 16))), new Version(39, new int[]{6, 26, 54, 82, 110, 138, 166}, new ECBlocks(30, new ECB(20, 117), new ECB(4, 118)), new ECBlocks(28, new ECB(40, 47), new ECB(7, 48)), new ECBlocks(30, new ECB(43, 24), new ECB(22, 25)), new ECBlocks(30, new ECB(10, 15), new ECB(67, 16))), new Version(40, new int[]{6, 30, 58, 86, 114, 142, 170}, new ECBlocks(30, new ECB(19, 118), new ECB(6, 119)), new ECBlocks(28, new ECB(18, 47), new ECB(31, 48)), new ECBlocks(30, new ECB(34, 24), new ECB(34, 25)), new ECBlocks(30, new ECB(20, 15), new ECB(61, 16))) }; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/detector/AlignmentPattern.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.detector; import com.google.zxing.ResultPoint; /** *Encapsulates an alignment pattern, which are the smaller square patterns found in * all but the simplest QR Codes.
* * @author Sean Owen */ public final class AlignmentPattern extends ResultPoint { private final float estimatedModuleSize; AlignmentPattern(float posX, float posY, float estimatedModuleSize) { super(posX, posY); this.estimatedModuleSize = estimatedModuleSize; } /** *Determines if this alignment pattern "about equals" an alignment pattern at the stated * position and size -- meaning, it is at nearly the same center with nearly the same size.
*/ boolean aboutEquals(float moduleSize, float i, float j) { if (Math.abs(i - getY()) <= moduleSize && Math.abs(j - getX()) <= moduleSize) { float moduleSizeDiff = Math.abs(moduleSize - estimatedModuleSize); return moduleSizeDiff <= 1.0f || moduleSizeDiff <= estimatedModuleSize; } return false; } /** * Combines this object's current estimate of a finder pattern position and module size * with a new estimate. It returns a new {@code FinderPattern} containing an average of the two. */ AlignmentPattern combineEstimate(float i, float j, float newModuleSize) { float combinedX = (getX() + j) / 2.0f; float combinedY = (getY() + i) / 2.0f; float combinedModuleSize = (estimatedModuleSize + newModuleSize) / 2.0f; return new AlignmentPattern(combinedX, combinedY, combinedModuleSize); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.detector; import com.google.zxing.NotFoundException; import com.google.zxing.ResultPointCallback; import com.google.zxing.common.BitMatrix; import java.util.ArrayList; import java.util.List; /** *This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder * patterns but are smaller and appear at regular intervals throughout the image.
* *At the moment this only looks for the bottom-right alignment pattern.
* *This is mostly a simplified copy of {@link FinderPatternFinder}. It is copied, * pasted and stripped down here for maximum performance but does unfortunately duplicate * some code.
* *This class is thread-safe but not reentrant. Each thread must allocate its own object.
* * @author Sean Owen */ final class AlignmentPatternFinder { private final BitMatrix image; private final ListCreates a finder that will look in a portion of the whole image.
* * @param image image to search * @param startX left column from which to start searching * @param startY top row from which to start searching * @param width width of region to search * @param height height of region to search * @param moduleSize estimated module size so far */ AlignmentPatternFinder(BitMatrix image, int startX, int startY, int width, int height, float moduleSize, ResultPointCallback resultPointCallback) { this.image = image; this.possibleCenters = new ArrayList<>(5); this.startX = startX; this.startY = startY; this.width = width; this.height = height; this.moduleSize = moduleSize; this.crossCheckStateCount = new int[3]; this.resultPointCallback = resultPointCallback; } /** *This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since * it's pretty performance-critical and so is written to be fast foremost.
* * @return {@link AlignmentPattern} if found * @throws NotFoundException if not found */ AlignmentPattern find() throws NotFoundException { int startX = this.startX; int height = this.height; int maxJ = startX + width; int middleI = startY + (height / 2); // We are looking for black/white/black modules in 1:1:1 ratio; // this tracks the number of black/white/black modules seen so far int[] stateCount = new int[3]; for (int iGen = 0; iGen < height; iGen++) { // Search from middle outwards int i = middleI + ((iGen & 0x01) == 0 ? (iGen + 1) / 2 : -((iGen + 1) / 2)); stateCount[0] = 0; stateCount[1] = 0; stateCount[2] = 0; int j = startX; // Burn off leading white pixels before anything else; if we start in the middle of // a white run, it doesn't make sense to count its length, since we don't know if the // white run continued to the left of the start point while (j < maxJ && !image.get(j, i)) { j++; } int currentState = 0; while (j < maxJ) { if (image.get(j, i)) { // Black pixel if (currentState == 1) { // Counting black pixels stateCount[1]++; } else { // Counting white pixels if (currentState == 2) { // A winner? if (foundPatternCross(stateCount)) { // Yes AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, j); if (confirmed != null) { return confirmed; } } stateCount[0] = stateCount[2]; stateCount[1] = 1; stateCount[2] = 0; currentState = 1; } else { stateCount[++currentState]++; } } } else { // White pixel if (currentState == 1) { // Counting black pixels currentState++; } stateCount[currentState]++; } j++; } if (foundPatternCross(stateCount)) { AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, maxJ); if (confirmed != null) { return confirmed; } } } // Hmm, nothing we saw was observed and confirmed twice. If we had // any guess at all, return it. if (!possibleCenters.isEmpty()) { return possibleCenters.get(0); } throw NotFoundException.getNotFoundInstance(); } /** * Given a count of black/white/black pixels just seen and an end position, * figures the location of the center of this black/white/black run. */ private static float centerFromEnd(int[] stateCount, int end) { return (end - stateCount[2]) - stateCount[1] / 2.0f; } /** * @param stateCount count of black/white/black pixels just read * @return true iff the proportions of the counts is close enough to the 1/1/1 ratios * used by alignment patterns to be considered a match */ private boolean foundPatternCross(int[] stateCount) { float moduleSize = this.moduleSize; float maxVariance = moduleSize / 2.0f; for (int i = 0; i < 3; i++) { if (Math.abs(moduleSize - stateCount[i]) >= maxVariance) { return false; } } return true; } /** *After a horizontal scan finds a potential alignment pattern, this method * "cross-checks" by scanning down vertically through the center of the possible * alignment pattern to see if the same proportion is detected.
* * @param startI row where an alignment pattern was detected * @param centerJ center of the section that appears to cross an alignment pattern * @param maxCount maximum reasonable number of modules that should be * observed in any reading state, based on the results of the horizontal scan * @return vertical center of alignment pattern, or {@link Float#NaN} if not found */ private float crossCheckVertical(int startI, int centerJ, int maxCount, int originalStateCountTotal) { BitMatrix image = this.image; int maxI = image.getHeight(); int[] stateCount = crossCheckStateCount; stateCount[0] = 0; stateCount[1] = 0; stateCount[2] = 0; // Start counting up from center int i = startI; while (i >= 0 && image.get(centerJ, i) && stateCount[1] <= maxCount) { stateCount[1]++; i--; } // If already too many modules in this state or ran off the edge: if (i < 0 || stateCount[1] > maxCount) { return Float.NaN; } while (i >= 0 && !image.get(centerJ, i) && stateCount[0] <= maxCount) { stateCount[0]++; i--; } if (stateCount[0] > maxCount) { return Float.NaN; } // Now also count down from center i = startI + 1; while (i < maxI && image.get(centerJ, i) && stateCount[1] <= maxCount) { stateCount[1]++; i++; } if (i == maxI || stateCount[1] > maxCount) { return Float.NaN; } while (i < maxI && !image.get(centerJ, i) && stateCount[2] <= maxCount) { stateCount[2]++; i++; } if (stateCount[2] > maxCount) { return Float.NaN; } int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2]; if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { return Float.NaN; } return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN; } /** *This is called when a horizontal scan finds a possible alignment pattern. It will * cross check with a vertical scan, and if successful, will see if this pattern had been * found on a previous horizontal scan. If so, we consider it confirmed and conclude we have * found the alignment pattern.
* * @param stateCount reading state module counts from horizontal scan * @param i row where alignment pattern may be found * @param j end of possible alignment pattern in row * @return {@link AlignmentPattern} if we have found the same pattern twice, or null if not */ private AlignmentPattern handlePossibleCenter(int[] stateCount, int i, int j) { int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2]; float centerJ = centerFromEnd(stateCount, j); float centerI = crossCheckVertical(i, (int) centerJ, 2 * stateCount[1], stateCountTotal); if (!Float.isNaN(centerI)) { float estimatedModuleSize = (stateCount[0] + stateCount[1] + stateCount[2]) / 3.0f; for (AlignmentPattern center : possibleCenters) { // Look for about the same center and module size: if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { return center.combineEstimate(centerI, centerJ, estimatedModuleSize); } } // Hadn't found this before; save it AlignmentPattern point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize); possibleCenters.add(point); if (resultPointCallback != null) { resultPointCallback.foundPossibleResultPoint(point); } } return null; } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/detector/Detector.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.detector; import com.google.zxing.DecodeHintType; import com.google.zxing.FormatException; import com.google.zxing.NotFoundException; import com.google.zxing.ResultPoint; import com.google.zxing.ResultPointCallback; import com.google.zxing.common.BitMatrix; import com.google.zxing.common.DetectorResult; import com.google.zxing.common.GridSampler; import com.google.zxing.common.PerspectiveTransform; import com.google.zxing.common.detector.MathUtils; import com.google.zxing.qrcode.decoder.Version; import java.util.Map; /** *Encapsulates logic that can detect a QR Code in an image, even if the QR Code * is rotated or skewed, or partially obscured.
* * @author Sean Owen */ public class Detector { private final BitMatrix image; private ResultPointCallback resultPointCallback; public Detector(BitMatrix image) { this.image = image; } protected final BitMatrix getImage() { return image; } protected final ResultPointCallback getResultPointCallback() { return resultPointCallback; } /** *Detects a QR Code in an image.
* * @return {@link DetectorResult} encapsulating results of detecting a QR Code * @throws NotFoundException if QR Code cannot be found * @throws FormatException if a QR Code cannot be decoded */ public DetectorResult detect() throws NotFoundException, FormatException { return detect(null); } /** *Detects a QR Code in an image.
* * @param hints optional hints to detector * @return {@link DetectorResult} encapsulating results of detecting a QR Code * @throws NotFoundException if QR Code cannot be found * @throws FormatException if a QR Code cannot be decoded */ public final DetectorResult detect(MapComputes the dimension (number of modules on a size) of the QR Code based on the position * of the finder patterns and estimated module size.
*/ private static int computeDimension(ResultPoint topLeft, ResultPoint topRight, ResultPoint bottomLeft, float moduleSize) throws NotFoundException { int tltrCentersDimension = MathUtils.round(ResultPoint.distance(topLeft, topRight) / moduleSize); int tlblCentersDimension = MathUtils.round(ResultPoint.distance(topLeft, bottomLeft) / moduleSize); int dimension = ((tltrCentersDimension + tlblCentersDimension) / 2) + 7; switch (dimension & 0x03) { // mod 4 case 0: dimension++; break; // 1? do nothing case 2: dimension--; break; case 3: throw NotFoundException.getNotFoundInstance(); } return dimension; } /** *Computes an average estimated module size based on estimated derived from the positions * of the three finder patterns.
* * @param topLeft detected top-left finder pattern center * @param topRight detected top-right finder pattern center * @param bottomLeft detected bottom-left finder pattern center * @return estimated module size */ protected final float calculateModuleSize(ResultPoint topLeft, ResultPoint topRight, ResultPoint bottomLeft) { // Take the average return (calculateModuleSizeOneWay(topLeft, topRight) + calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f; } /** *Estimates module size based on two finder patterns -- it uses * {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the * width of each, measuring along the axis between their centers.
*/ private float calculateModuleSizeOneWay(ResultPoint pattern, ResultPoint otherPattern) { float moduleSizeEst1 = sizeOfBlackWhiteBlackRunBothWays((int) pattern.getX(), (int) pattern.getY(), (int) otherPattern.getX(), (int) otherPattern.getY()); float moduleSizeEst2 = sizeOfBlackWhiteBlackRunBothWays((int) otherPattern.getX(), (int) otherPattern.getY(), (int) pattern.getX(), (int) pattern.getY()); if (Float.isNaN(moduleSizeEst1)) { return moduleSizeEst2 / 7.0f; } if (Float.isNaN(moduleSizeEst2)) { return moduleSizeEst1 / 7.0f; } // Average them, and divide by 7 since we've counted the width of 3 black modules, // and 1 white and 1 black module on either side. Ergo, divide sum by 14. return (moduleSizeEst1 + moduleSizeEst2) / 14.0f; } /** * See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of * a finder pattern by looking for a black-white-black run from the center in the direction * of another point (another finder pattern center), and in the opposite direction too. */ private float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY) { float result = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY); // Now count other way -- don't run off image though of course float scale = 1.0f; int otherToX = fromX - (toX - fromX); if (otherToX < 0) { scale = fromX / (float) (fromX - otherToX); otherToX = 0; } else if (otherToX >= image.getWidth()) { scale = (image.getWidth() - 1 - fromX) / (float) (otherToX - fromX); otherToX = image.getWidth() - 1; } int otherToY = (int) (fromY - (toY - fromY) * scale); scale = 1.0f; if (otherToY < 0) { scale = fromY / (float) (fromY - otherToY); otherToY = 0; } else if (otherToY >= image.getHeight()) { scale = (image.getHeight() - 1 - fromY) / (float) (otherToY - fromY); otherToY = image.getHeight() - 1; } otherToX = (int) (fromX + (otherToX - fromX) * scale); result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY); // Middle pixel is double-counted this way; subtract 1 return result - 1.0f; } /** *This method traces a line from a point in the image, in the direction towards another point. * It begins in a black region, and keeps going until it finds white, then black, then white again. * It reports the distance from the start to this point.
* *This is used when figuring out how wide a finder pattern is, when the finder pattern * may be skewed or rotated.
*/ private float sizeOfBlackWhiteBlackRun(int fromX, int fromY, int toX, int toY) { // Mild variant of Bresenham's algorithm; // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm boolean steep = Math.abs(toY - fromY) > Math.abs(toX - fromX); if (steep) { int temp = fromX; fromX = fromY; fromY = temp; temp = toX; toX = toY; toY = temp; } int dx = Math.abs(toX - fromX); int dy = Math.abs(toY - fromY); int error = -dx / 2; int xstep = fromX < toX ? 1 : -1; int ystep = fromY < toY ? 1 : -1; // In black pixels, looking for white, first or second time. int state = 0; // Loop up until x == toX, but not beyond int xLimit = toX + xstep; for (int x = fromX, y = fromY; x != xLimit; x += xstep) { int realX = steep ? y : x; int realY = steep ? x : y; // Does current pixel mean we have moved white to black or vice versa? // Scanning black in state 0,2 and white in state 1, so if we find the wrong // color, advance to next state or end if we are in state 2 already if ((state == 1) == image.get(realX, realY)) { if (state == 2) { return MathUtils.distance(x, y, fromX, fromY); } state++; } error += dy; if (error > 0) { if (y == toY) { break; } y += ystep; error -= dx; } } // Found black-white-black; give the benefit of the doubt that the next pixel outside the image // is "white" so this last point at (toX+xStep,toY) is the right ending. This is really a // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this. if (state == 2) { return MathUtils.distance(toX + xstep, toY, fromX, fromY); } // else we didn't find even black-white-black; no estimate is really possible return Float.NaN; } /** *Attempts to locate an alignment pattern in a limited region of the image, which is * guessed to contain it. This method uses {@link AlignmentPattern}.
* * @param overallEstModuleSize estimated module size so far * @param estAlignmentX x coordinate of center of area probably containing alignment pattern * @param estAlignmentY y coordinate of above * @param allowanceFactor number of pixels in all directions to search from the center * @return {@link AlignmentPattern} if found, or null otherwise * @throws NotFoundException if an unexpected error occurs during detection */ protected final AlignmentPattern findAlignmentInRegion(float overallEstModuleSize, int estAlignmentX, int estAlignmentY, float allowanceFactor) throws NotFoundException { // Look for an alignment pattern (3 modules in size) around where it // should be int allowance = (int) (allowanceFactor * overallEstModuleSize); int alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance); int alignmentAreaRightX = Math.min(image.getWidth() - 1, estAlignmentX + allowance); if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) { throw NotFoundException.getNotFoundInstance(); } int alignmentAreaTopY = Math.max(0, estAlignmentY - allowance); int alignmentAreaBottomY = Math.min(image.getHeight() - 1, estAlignmentY + allowance); if (alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize * 3) { throw NotFoundException.getNotFoundInstance(); } AlignmentPatternFinder alignmentFinder = new AlignmentPatternFinder( image, alignmentAreaLeftX, alignmentAreaTopY, alignmentAreaRightX - alignmentAreaLeftX, alignmentAreaBottomY - alignmentAreaTopY, overallEstModuleSize, resultPointCallback); return alignmentFinder.find(); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/detector/FinderPattern.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.detector; import com.google.zxing.ResultPoint; /** *Encapsulates a finder pattern, which are the three square patterns found in * the corners of QR Codes. It also encapsulates a count of similar finder patterns, * as a convenience to the finder's bookkeeping.
* * @author Sean Owen */ public final class FinderPattern extends ResultPoint { private final float estimatedModuleSize; private final int count; FinderPattern(float posX, float posY, float estimatedModuleSize) { this(posX, posY, estimatedModuleSize, 1); } private FinderPattern(float posX, float posY, float estimatedModuleSize, int count) { super(posX, posY); this.estimatedModuleSize = estimatedModuleSize; this.count = count; } public float getEstimatedModuleSize() { return estimatedModuleSize; } int getCount() { return count; } /* void incrementCount() { this.count++; } */ /** *Determines if this finder pattern "about equals" a finder pattern at the stated * position and size -- meaning, it is at nearly the same center with nearly the same size.
*/ boolean aboutEquals(float moduleSize, float i, float j) { if (Math.abs(i - getY()) <= moduleSize && Math.abs(j - getX()) <= moduleSize) { float moduleSizeDiff = Math.abs(moduleSize - estimatedModuleSize); return moduleSizeDiff <= 1.0f || moduleSizeDiff <= estimatedModuleSize; } return false; } /** * Combines this object's current estimate of a finder pattern position and module size * with a new estimate. It returns a new {@code FinderPattern} containing a weighted average * based on count. */ FinderPattern combineEstimate(float i, float j, float newModuleSize) { int combinedCount = count + 1; float combinedX = (count * getX() + j) / combinedCount; float combinedY = (count * getY() + i) / combinedCount; float combinedModuleSize = (count * estimatedModuleSize + newModuleSize) / combinedCount; return new FinderPattern(combinedX, combinedY, combinedModuleSize, combinedCount); } } ================================================ FILE: scanner/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java ================================================ /* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.detector; import com.google.zxing.DecodeHintType; import com.google.zxing.NotFoundException; import com.google.zxing.ResultPoint; import com.google.zxing.ResultPointCallback; import com.google.zxing.common.BitMatrix; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; /** *This class attempts to find finder patterns in a QR Code. Finder patterns are the square * markers at three corners of a QR Code.
* *This class is thread-safe but not reentrant. Each thread must allocate its own object.
*
* @author Sean Owen
*/
public class FinderPatternFinder {
private static final int CENTER_QUORUM = 2;
protected static final int MIN_SKIP = 3; // 1 pixel/module times 3 modules/center
protected static final int MAX_MODULES = 97; // support up to version 20 for mobile clients
private final BitMatrix image;
private final List Creates a finder that will search the image for three finder patterns. After a horizontal scan finds a potential finder pattern, this method
* "cross-checks" by scanning down vertically through the center of the possible
* finder pattern to see if the same proportion is detected. Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical,
* except it reads horizontally instead of vertically. This is used to cross-cross
* check a vertical cross check and locate the real center of the alignment pattern. This is called when a horizontal scan finds a possible alignment pattern. It will
* cross check with a vertical scan, and if successful, will, ah, cross-cross-check
* with another horizontal scan. This is needed primarily to locate the real horizontal
* center of the pattern in cases of extreme skew.
* And then we cross-cross-cross check with another diagonal scan. If that succeeds the finder pattern location is added to a list that tracks
* the number of times each location has been nearly-matched as a finder pattern.
* Each additional find is more evidence that the location is in fact a finder
* pattern center
*
* @param stateCount reading state module counts from horizontal scan
* @param i row where finder pattern may be found
* @param j end of possible finder pattern in row
* @return true if a finder pattern candidate was found this time
*/
protected final boolean handlePossibleCenter(int[] stateCount, int i, int j) {
int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] +
stateCount[4];
float centerJ = centerFromEnd(stateCount, j);
float centerI = crossCheckVertical(i, (int) centerJ, stateCount[2], stateCountTotal);
if (!Float.isNaN(centerI)) {
// Re-cross check
centerJ = crossCheckHorizontal((int) centerJ, (int) centerI, stateCount[2], stateCountTotal);
if (!Float.isNaN(centerJ) && crossCheckDiagonal((int) centerI, (int) centerJ)) {
float estimatedModuleSize = stateCountTotal / 7.0f;
boolean found = false;
for (int index = 0; index < possibleCenters.size(); index++) {
FinderPattern center = possibleCenters.get(index);
// Look for about the same center and module size:
if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
possibleCenters.set(index, center.combineEstimate(centerI, centerJ, estimatedModuleSize));
found = true;
break;
}
}
if (!found) {
FinderPattern point = new FinderPattern(centerJ, centerI, estimatedModuleSize);
possibleCenters.add(point);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(point);
}
}
return true;
}
}
return false;
}
/**
* @return number of rows we could safely skip during scanning, based on the first
* two finder patterns that have been located. In some cases their position will
* allow us to infer that the third pattern must lie below a certain point farther
* down in the image.
*/
private int findRowSkip() {
int max = possibleCenters.size();
if (max <= 1) {
return 0;
}
ResultPoint firstConfirmedCenter = null;
for (FinderPattern center : possibleCenters) {
if (center.getCount() >= CENTER_QUORUM) {
if (firstConfirmedCenter == null) {
firstConfirmedCenter = center;
} else {
// We have two confirmed centers
// How far down can we skip before resuming looking for the next
// pattern? In the worst case, only the difference between the
// difference in the x / y coordinates of the two centers.
// This is the case where you find top left last.
hasSkipped = true;
return (int) (Math.abs(firstConfirmedCenter.getX() - center.getX()) -
Math.abs(firstConfirmedCenter.getY() - center.getY())) / 2;
}
}
}
return 0;
}
/**
* @return true iff we have found at least 3 finder patterns that have been detected
* at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the
* candidates is "pretty similar"
*/
private boolean haveMultiplyConfirmedCenters() {
int confirmedCount = 0;
float totalModuleSize = 0.0f;
int max = possibleCenters.size();
for (FinderPattern pattern : possibleCenters) {
if (pattern.getCount() >= CENTER_QUORUM) {
confirmedCount++;
totalModuleSize += pattern.getEstimatedModuleSize();
}
}
if (confirmedCount < 3) {
return false;
}
// OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"
// and that we need to keep looking. We detect this by asking if the estimated module sizes
// vary too much. We arbitrarily say that when the total deviation from average exceeds
// 5% of the total module size estimates, it's too much.
float average = totalModuleSize / max;
float totalDeviation = 0.0f;
for (FinderPattern pattern : possibleCenters) {
totalDeviation += Math.abs(pattern.getEstimatedModuleSize() - average);
}
return totalDeviation <= 0.05f * totalModuleSize;
}
/**
* @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are
* those that have been detected at least {@link #CENTER_QUORUM} times, and whose module
* size differs from the average among those patterns the least
* @throws NotFoundException if 3 such finder patterns do not exist
*/
private FinderPattern[] selectBestPatterns() throws NotFoundException {
int startSize = possibleCenters.size();
if (startSize < 3) {
// Couldn't find enough finder patterns
throw NotFoundException.getNotFoundInstance();
}
// Filter outlier possibilities whose module size is too different
if (startSize > 3) {
// But we can only afford to do so if we have at least 4 possibilities to choose from
double totalModuleSize = 0.0;
double square = 0.0;
for (FinderPattern center : possibleCenters) {
float size = center.getEstimatedModuleSize();
totalModuleSize += size;
square += size * size;
}
double average = totalModuleSize / startSize;
float stdDev = (float) Math.sqrt(square / startSize - average * average);
Collections.sort(possibleCenters, new FurthestFromAverageComparator((float) average));
float limit = Math.max(0.2f * (float) average, stdDev);
for (int i = 0; i < possibleCenters.size() && possibleCenters.size() > 3; i++) {
FinderPattern pattern = possibleCenters.get(i);
if (Math.abs(pattern.getEstimatedModuleSize() - average) > limit) {
possibleCenters.remove(i);
i--;
}
}
}
if (possibleCenters.size() > 3) {
// Throw away all but those first size candidate points we found.
float totalModuleSize = 0.0f;
for (FinderPattern possibleCenter : possibleCenters) {
totalModuleSize += possibleCenter.getEstimatedModuleSize();
}
float average = totalModuleSize / possibleCenters.size();
Collections.sort(possibleCenters, new CenterComparator(average));
possibleCenters.subList(3, possibleCenters.size()).clear();
}
return new FinderPattern[]{
possibleCenters.get(0),
possibleCenters.get(1),
possibleCenters.get(2)
};
}
/**
* Orders by furthest from average Orders by {@link FinderPattern#getCount()}, descending. Encapsulates information about finder patterns in an image, including the location of
* the three finder patterns, and their estimated module size.
* author: Blankj
* blog : http://blankj.com
* time : 2017/12/29
* desc : constants of permission
*
*/
@SuppressLint("InlinedApi")
public final class PermissionConstants {
public static final String CALENDAR = Manifest.permission_group.CALENDAR;
public static final String CAMERA = Manifest.permission_group.CAMERA;
public static final String CONTACTS = Manifest.permission_group.CONTACTS;
public static final String LOCATION = Manifest.permission_group.LOCATION;
public static final String MICROPHONE = Manifest.permission_group.MICROPHONE;
public static final String PHONE = Manifest.permission_group.PHONE;
public static final String SENSORS = Manifest.permission_group.SENSORS;
public static final String SMS = Manifest.permission_group.SMS;
public static final String STORAGE = Manifest.permission_group.STORAGE;
private static final String[] GROUP_CALENDAR = {
permission.READ_CALENDAR, permission.WRITE_CALENDAR
};
private static final String[] GROUP_CAMERA = {
permission.CAMERA
};
private static final String[] GROUP_CONTACTS = {
permission.READ_CONTACTS, permission.WRITE_CONTACTS, permission.GET_ACCOUNTS
};
private static final String[] GROUP_LOCATION = {
permission.ACCESS_FINE_LOCATION, permission.ACCESS_COARSE_LOCATION
};
private static final String[] GROUP_MICROPHONE = {
permission.RECORD_AUDIO
};
private static final String[] GROUP_PHONE = {
permission.READ_PHONE_STATE, permission.CALL_PHONE
, permission.READ_CALL_LOG, permission.WRITE_CALL_LOG,
permission.ADD_VOICEMAIL, permission.USE_SIP, permission.PROCESS_OUTGOING_CALLS
};
private static final String[] GROUP_SENSORS = {
permission.BODY_SENSORS
};
private static final String[] GROUP_SMS = {
permission.SEND_SMS, permission.RECEIVE_SMS, permission.READ_SMS,
permission.RECEIVE_WAP_PUSH, permission.RECEIVE_MMS,
};
private static final String[] GROUP_STORAGE = {
permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE
};
@StringDef({CALENDAR, CAMERA, CONTACTS, LOCATION, MICROPHONE, PHONE, SENSORS, SMS, STORAGE,})
@Retention(RetentionPolicy.SOURCE)
public @interface Permission {
}
public static String[] getPermissions(@Permission final String permission) {
switch (permission) {
case CALENDAR:
return GROUP_CALENDAR;
case CAMERA:
return GROUP_CAMERA;
case CONTACTS:
return GROUP_CONTACTS;
case LOCATION:
return GROUP_LOCATION;
case MICROPHONE:
return GROUP_MICROPHONE;
case PHONE:
return GROUP_PHONE;
case SENSORS:
return GROUP_SENSORS;
case SMS:
return GROUP_SMS;
case STORAGE:
return GROUP_STORAGE;
}
return new String[]{permission};
}
}
================================================
FILE: scanner/src/main/java/com/nanchen/scanner/utils/PermissionUtils.java
================================================
package com.nanchen.scanner.utils;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
*
* author: Blankj
* blog : http://blankj.com
* time : 2017/12/29
* desc : utils about permission
*
*/
public final class PermissionUtils {
private static List
{@code false}: no
*/
public static boolean isGranted(final String... permissions) {
for (String permission : permissions) {
if (!isGranted(permission)) {
return false;
}
}
return true;
}
private static boolean isGranted(final String permission) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
|| PackageManager.PERMISSION_GRANTED
== ContextCompat.checkSelfPermission(mApp, permission);
}
/**
* Launch the application's details settings.
*/
public static void launchAppDetailsSettings() {
Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
intent.setData(Uri.parse("package:" + mApp.getPackageName()));
mApp.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
/**
* Set the permissions.
*
* @param permissions The permissions.
* @return the single {@link PermissionUtils} instance
*/
public static PermissionUtils permission(Context mContext, @PermissionConstants.Permission final String...
permissions
) {
mApp = mContext;
PERMISSIONS = getPermissions();
return new PermissionUtils(permissions);
}
private PermissionUtils(final String... permissions) {
mPermissions = new LinkedHashSet<>();
for (String permission : permissions) {
for (String aPermission : PermissionConstants.getPermissions(permission)) {
if (PERMISSIONS.contains(aPermission)) {
mPermissions.add(aPermission);
}
}
}
sInstance = this;
}
/**
* Set rationale listener.
*
* @param listener The rationale listener.
* @return the single {@link PermissionUtils} instance
*/
public PermissionUtils rationale(final OnRationaleListener listener) {
mOnRationaleListener = listener;
return this;
}
/**
* Set the simple call back.
*
* @param callback the simple call back
* @return the single {@link PermissionUtils} instance
*/
public PermissionUtils callback(final SimpleCallback callback) {
mSimpleCallback = callback;
return this;
}
/**
* Set the full call back.
*
* @param callback the full call back
* @return the single {@link PermissionUtils} instance
*/
public PermissionUtils callback(final FullCallback callback) {
mFullCallback = callback;
return this;
}
/**
* Set the theme callback.
*
* @param callback The theme callback.
* @return the single {@link PermissionUtils} instance
*/
public PermissionUtils theme(final ThemeCallback callback) {
mThemeCallback = callback;
return this;
}
/**
* Start request.
*/
public void request() {
mPermissionsGranted = new ArrayList<>();
mPermissionsRequest = new ArrayList<>();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
mPermissionsGranted.addAll(mPermissions);
requestCallback();
} else {
for (String permission : mPermissions) {
if (isGranted(permission)) {
mPermissionsGranted.add(permission);
} else {
mPermissionsRequest.add(permission);
}
}
if (mPermissionsRequest.isEmpty()) {
requestCallback();
} else {
startPermissionActivity();
}
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void startPermissionActivity() {
mPermissionsDenied = new ArrayList<>();
mPermissionsDeniedForever = new ArrayList<>();
PermissionActivity.start(mApp);
}
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean rationale(final Activity activity) {
boolean isRationale = false;
if (mOnRationaleListener != null) {
for (String permission : mPermissionsRequest) {
if (activity.shouldShowRequestPermissionRationale(permission)) {
getPermissionsStatus(activity);
mOnRationaleListener.rationale(new OnRationaleListener.ShouldRequest() {
@Override
public void again(boolean again) {
if (again) {
startPermissionActivity();
} else {
requestCallback();
}
}
});
isRationale = true;
break;
}
}
mOnRationaleListener = null;
}
return isRationale;
}
private void getPermissionsStatus(final Activity activity) {
for (String permission : mPermissionsRequest) {
if (isGranted(permission)) {
mPermissionsGranted.add(permission);
} else {
mPermissionsDenied.add(permission);
if (!activity.shouldShowRequestPermissionRationale(permission)) {
mPermissionsDeniedForever.add(permission);
}
}
}
}
private void requestCallback() {
if (mSimpleCallback != null) {
if (mPermissionsRequest.size() == 0
|| mPermissions.size() == mPermissionsGranted.size()) {
mSimpleCallback.onGranted();
} else {
if (!mPermissionsDenied.isEmpty()) {
mSimpleCallback.onDenied();
}
}
mSimpleCallback = null;
}
if (mFullCallback != null) {
if (mPermissionsRequest.size() == 0
|| mPermissions.size() == mPermissionsGranted.size()) {
mFullCallback.onGranted(mPermissionsGranted);
} else {
if (!mPermissionsDenied.isEmpty()) {
mFullCallback.onDenied(mPermissionsDeniedForever, mPermissionsDenied);
}
}
mFullCallback = null;
}
mOnRationaleListener = null;
mThemeCallback = null;
}
private void onRequestPermissionsResult(final Activity activity) {
getPermissionsStatus(activity);
requestCallback();
}
@RequiresApi(api = Build.VERSION_CODES.M)
public static class PermissionActivity extends Activity {
public static void start(final Context context) {
Intent starter = new Intent(context, PermissionActivity.class);
starter.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(starter);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
getWindow().setStatusBarColor(Color.TRANSPARENT);
if (sInstance == null) {
super.onCreate(savedInstanceState);
Log.e("PermissionUtils", "request permissions failed");
finish();
return;
}
if (sInstance.mThemeCallback != null) {
sInstance.mThemeCallback.onActivityCreate(this);
}
super.onCreate(savedInstanceState);
if (sInstance.rationale(this)) {
finish();
return;
}
if (sInstance.mPermissionsRequest != null) {
int size = sInstance.mPermissionsRequest.size();
if (size <= 0) {
finish();
return;
}
requestPermissions(sInstance.mPermissionsRequest.toArray(new String[size]), 1);
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
sInstance.onRequestPermissionsResult(this);
finish();
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
finish();
return true;
}
}
///////////////////////////////////////////////////////////////////////////
// interface
///////////////////////////////////////////////////////////////////////////
public interface OnRationaleListener {
void rationale(ShouldRequest shouldRequest);
interface ShouldRequest {
void again(boolean again);
}
}
public interface SimpleCallback {
void onGranted();
void onDenied();
}
public interface FullCallback {
void onGranted(List
{@code false}: no
*/
public static boolean isFullScreen(@NonNull final Activity activity) {
int fullScreenFlag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
return (activity.getWindow().getAttributes().flags & fullScreenFlag) == fullScreenFlag;
}
/**
* Set the screen to landscape.
*
* @param activity The activity.
*/
public static void setLandscape(@NonNull final Activity activity) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
/**
* Set the screen to portrait.
*
* @param activity The activity.
*/
public static void setPortrait(@NonNull final Activity activity) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
/**
* Return whether screen is landscape.
*
* @return {@code true}: yes
{@code false}: no
*/
public static boolean isLandscape(Context context) {
return context.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
}
/**
* Return whether screen is portrait.
*
* @return {@code true}: yes
{@code false}: no
*/
public static boolean isPortrait(Context context) {
return context.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_PORTRAIT;
}
/**
* Return the rotation of screen.
*
* @param activity The activity.
* @return the rotation of screen
*/
public static int getScreenRotation(@NonNull final Activity activity) {
switch (activity.getWindowManager().getDefaultDisplay().getRotation()) {
case Surface.ROTATION_0:
return 0;
case Surface.ROTATION_90:
return 90;
case Surface.ROTATION_180:
return 180;
case Surface.ROTATION_270:
return 270;
default:
return 0;
}
}
/**
* Return the bitmap of screen.
*
* @param activity The activity.
* @return the bitmap of screen
*/
public static Bitmap screenShot(@NonNull final Activity activity) {
return screenShot(activity, false);
}
/**
* Return the bitmap of screen.
*
* @param activity The activity.
* @param isDeleteStatusBar True to delete status bar, false otherwise.
* @return the bitmap of screen
*/
public static Bitmap screenShot(@NonNull final Activity activity, boolean isDeleteStatusBar) {
View decorView = activity.getWindow().getDecorView();
decorView.setDrawingCacheEnabled(true);
decorView.setWillNotCacheDrawing(false);
Bitmap bmp = decorView.getDrawingCache();
if (bmp == null) return null;
DisplayMetrics dm = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
Bitmap ret;
if (isDeleteStatusBar) {
Resources resources = activity.getResources();
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
int statusBarHeight = resources.getDimensionPixelSize(resourceId);
ret = Bitmap.createBitmap(
bmp,
0,
statusBarHeight,
dm.widthPixels,
dm.heightPixels - statusBarHeight
);
} else {
ret = Bitmap.createBitmap(bmp, 0, 0, dm.widthPixels, dm.heightPixels);
}
decorView.destroyDrawingCache();
return ret;
}
/**
* Return whether screen is locked.
*
* @return {@code true}: yes
{@code false}: no
*/
public static boolean isScreenLock(Context context) {
KeyguardManager km =
(KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
//noinspection ConstantConditions
return km.inKeyguardRestrictedInputMode();
}
/**
* Return the duration of sleep.
*
* @return the duration of sleep.
*/
public static int getSleepDuration(Context context) {
try {
return Settings.System.getInt(
context.getContentResolver(),
Settings.System.SCREEN_OFF_TIMEOUT
);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
return -123;
}
}
/**
* Return whether device is tablet.
*
* @return {@code true}: yes
{@code false}: no
*/
public static boolean isTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
}
================================================
FILE: scanner/src/main/java/com/nanchen/scanner/zxing/AmbientLightManager.java
================================================
/*
* Copyright (C) 2012 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.nanchen.scanner.zxing;
import android.content.Context;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.preference.PreferenceManager;
import com.nanchen.scanner.zxing.camera.CameraManager;
import com.nanchen.scanner.zxing.camera.FrontLightMode;
/**
* Detects ambient light and switches on the front light when very dark, and off again when sufficiently light.
*
* @author Sean Owen
* @author Nikolaus Huber
*/
final class AmbientLightManager implements SensorEventListener {
private static final float TOO_DARK_LUX = 45.0f;
private static final float BRIGHT_ENOUGH_LUX = 450.0f;
private final Context context;
private CameraManager cameraManager;
private Sensor lightSensor;
AmbientLightManager(Context context) {
this.context = context;
}
void start(CameraManager cameraManager) {
this.cameraManager = cameraManager;
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
if (FrontLightMode.readPref(sharedPrefs) == FrontLightMode.AUTO) {
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
if (lightSensor != null) {
sensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
}
void stop() {
if (lightSensor != null) {
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
sensorManager.unregisterListener(this);
cameraManager = null;
lightSensor = null;
}
}
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
float ambientLightLux = sensorEvent.values[0];
if (cameraManager != null) {
if (ambientLightLux <= TOO_DARK_LUX) {
cameraManager.setTorch(true);
} else if (ambientLightLux >= BRIGHT_ENOUGH_LUX) {
cameraManager.setTorch(false);
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// do nothing
}
}
================================================
FILE: scanner/src/main/java/com/nanchen/scanner/zxing/BaseCaptureActivity.java
================================================
/*
* Copyright (C) 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nanchen.scanner.zxing;
import com.nanchen.scanner.zxing.camera.CameraManager;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Handler;
import android.view.SurfaceHolder;
/**
* This activity opens the camera and does the actual scanning on a background thread. It draws a
* viewfinder to help the user place the barcode correctly, shows feedback as the image processing
* is happening, and then overlays the results when a scan is successful.
*
* @author dswitkin@google.com (Daniel Switkin)
* @author Sean Owen
*/
public abstract class BaseCaptureActivity extends Activity implements SurfaceHolder.Callback {
public abstract ViewfinderView getViewfinderView();
public abstract Handler getHandler();
public abstract CameraManager getCameraManager();
public abstract void handleDecode(String result, Bitmap barcode, float scaleFactor);
public abstract void drawViewfinder();
}
================================================
FILE: scanner/src/main/java/com/nanchen/scanner/zxing/BeepManager.java
================================================
/*
* Copyright (C) 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.nanchen.scanner.zxing;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.util.Log;
import com.nanchen.scanner.R;
import java.io.Closeable;
import java.io.IOException;
/**
* Manages beeps and vibrations for {@link BaseCaptureActivity}.
*/
public final class BeepManager implements MediaPlayer.OnErrorListener, Closeable {
private static final String TAG = BeepManager.class.getSimpleName();
private static final float BEEP_VOLUME = 0.10f;
private static final long VIBRATE_DURATION = 200L;
private final Activity activity;
private MediaPlayer mediaPlayer;
private boolean playBeep;
private boolean vibrate;
public BeepManager(Activity activity) {
this.activity = activity;
this.mediaPlayer = null;
updatePrefs();
}
public synchronized void updatePrefs() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
playBeep = shouldBeep(prefs, activity);
// vibrate = prefs.getBoolean(PreferencesActivity.KEY_VIBRATE, false);
vibrate = true;
if (playBeep && mediaPlayer == null) {
// The volume on STREAM_SYSTEM is not adjustable, and users found it too loud,
// so we now play on the music stream.
activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
mediaPlayer = buildMediaPlayer(activity);
}
}
public synchronized void playBeepSoundAndVibrate() {
if (playBeep && mediaPlayer != null) {
mediaPlayer.start();
}
if (vibrate) {
Vibrator vibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(VIBRATE_DURATION);
}
}
private static boolean shouldBeep(SharedPreferences prefs, Context activity) {
// boolean shouldPlayBeep = prefs.getBoolean(PreferencesActivity.KEY_PLAY_BEEP, true);
// 直接true
boolean shouldPlayBeep = true;
if (shouldPlayBeep) {
// See if sound settings overrides this
AudioManager audioService = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
shouldPlayBeep = false;
}
}
return shouldPlayBeep;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private MediaPlayer buildMediaPlayer(Context activity) {
MediaPlayer mediaPlayer = new MediaPlayer();
try (AssetFileDescriptor file = activity.getResources().openRawResourceFd(R.raw.beep)) {
mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength());
mediaPlayer.setOnErrorListener(this);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setLooping(false);
mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
mediaPlayer.prepare();
return mediaPlayer;
} catch (IOException ioe) {
Log.w(TAG, ioe);
mediaPlayer.release();
return null;
}
}
@Override
public synchronized boolean onError(MediaPlayer mp, int what, int extra) {
if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
// we are finished, so put up an appropriate error toast if required and finish
activity.finish();
} else {
// possibly media player error, so release and recreate
close();
updatePrefs();
}
return true;
}
@Override
public synchronized void close() {
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
}
================================================
FILE: scanner/src/main/java/com/nanchen/scanner/zxing/CaptureActivityHandler.java
================================================
/*
* Copyright (C) 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nanchen.scanner.zxing;
import android.content.ActivityNotFoundException;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.BitmapFactory;
import android.provider.Browser;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.nanchen.scanner.R;
import com.nanchen.scanner.zxing.camera.CameraManager;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* This class handles all the messaging which comprises the state machine for capture.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class CaptureActivityHandler extends Handler {
private static final String TAG = CaptureActivityHandler.class.getSimpleName();
private final BaseCaptureActivity activity;
private final DecodeThread decodeThread;
private State state;
private final CameraManager cameraManager;
private enum State {
PREVIEW,
SUCCESS,
DONE
}
public CaptureActivityHandler(BaseCaptureActivity activity,
CameraManager cameraManager) {
this.activity = activity;
decodeThread = new DecodeThread(activity);
decodeThread.start();
state = State.SUCCESS;
// Start ourselves capturing previews and decoding.
this.cameraManager = cameraManager;
cameraManager.startPreview();
restartPreviewAndDecode();
}
@Override
public void handleMessage(Message message) {
if (message.what == R.id.restart_preview) {
restartPreviewAndDecode();
} else if (message.what == R.id.decode_succeeded) {
state = State.SUCCESS;
Bundle bundle = message.getData();
Bitmap barcode = null;
float scaleFactor = 1.0f;
if (bundle != null) {
byte[] compressedBitmap = bundle.getByteArray(DecodeThread.BARCODE_BITMAP);
if (compressedBitmap != null) {
barcode = BitmapFactory.decodeByteArray(compressedBitmap, 0, compressedBitmap.length, null);
// Mutable copy:
barcode = barcode.copy(Bitmap.Config.ARGB_8888, true);
}
scaleFactor = bundle.getFloat(DecodeThread.BARCODE_SCALED_FACTOR);
}
activity.handleDecode((String) message.obj, barcode, scaleFactor);
} else if (message.what == R.id.decode_failed) {// We're decoding as fast as possible, so when one decode fails, start another.
state = State.PREVIEW;
cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
} else if (message.what == R.id.return_scan_result) {
activity.setResult(Activity.RESULT_OK, (Intent) message.obj);
activity.finish();
} else if (message.what == R.id.launch_product_query) {
String url = (String) message.obj;
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intents.FLAG_NEW_DOC);
intent.setData(Uri.parse(url));
ResolveInfo resolveInfo =
activity.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
String browserPackageName = null;
if (resolveInfo != null && resolveInfo.activityInfo != null) {
browserPackageName = resolveInfo.activityInfo.packageName;
Log.d(TAG, "Using browser in package " + browserPackageName);
}
// Needed for default Android browser / Chrome only apparently
if (browserPackageName != null) {
switch (browserPackageName) {
case "com.android.browser":
case "com.android.chrome":
intent.setPackage(browserPackageName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, browserPackageName);
break;
}
}
try {
activity.startActivity(intent);
} catch (ActivityNotFoundException ignored) {
Log.w(TAG, "Can't find anything to handle VIEW of URI");
}
}
}
public void quitSynchronously() {
state = State.DONE;
cameraManager.stopPreview();
Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit);
quit.sendToTarget();
try {
// Wait at most half a second; should be enough time, and onPause() will timeout quickly
decodeThread.join(500L);
} catch (InterruptedException e) {
// continue
}
// Be absolutely sure we don't send any queued up messages
removeMessages(R.id.decode_succeeded);
removeMessages(R.id.decode_failed);
}
private void restartPreviewAndDecode() {
if (state == State.SUCCESS) {
state = State.PREVIEW;
cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
activity.drawViewfinder();
}
}
}
================================================
FILE: scanner/src/main/java/com/nanchen/scanner/zxing/Contents.java
================================================
/*
* Copyright (C) 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nanchen.scanner.zxing;
import android.provider.ContactsContract;
/**
* The set of constants to use when sending Barcode Scanner an Intent which requests a barcode
* to be encoded.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class Contents {
private Contents() {
}
/**
* Contains type constants used when sending Intents.
*/
public static final class Type {
/**
* Plain text. Use Intent.putExtra(DATA, string). This can be used for URLs too, but string
* must include "http://" or "https://".
*/
public static final String TEXT = "TEXT_TYPE";
/**
* An email type. Use Intent.putExtra(DATA, string) where string is the email address.
*/
public static final String EMAIL = "EMAIL_TYPE";
/**
* Use Intent.putExtra(DATA, string) where string is the phone number to call.
*/
public static final String PHONE = "PHONE_TYPE";
/**
* An SMS type. Use Intent.putExtra(DATA, string) where string is the number to SMS.
*/
public static final String SMS = "SMS_TYPE";
/**
* A contact. Send a request to encode it as follows:
* {@code
* import android.provider.Contacts;
*
* Intent intent = new Intent(Intents.Encode.ACTION);
* intent.putExtra(Intents.Encode.TYPE, CONTACT);
* Bundle bundle = new Bundle();
* bundle.putString(ContactsContract.Intents.Insert.NAME, "Jenny");
* bundle.putString(ContactsContract.Intents.Insert.PHONE, "8675309");
* bundle.putString(ContactsContract.Intents.Insert.EMAIL, "jenny@the80s.com");
* bundle.putString(ContactsContract.Intents.Insert.POSTAL, "123 Fake St. San Francisco, CA 94102");
* intent.putExtra(Intents.Encode.DATA, bundle);
* }
*/
public static final String CONTACT = "CONTACT_TYPE";
/**
* A geographic location. Use as follows:
* Bundle bundle = new Bundle();
* bundle.putFloat("LAT", latitude);
* bundle.putFloat("LONG", longitude);
* intent.putExtra(Intents.Encode.DATA, bundle);
*/
public static final String LOCATION = "LOCATION_TYPE";
private Type() {
}
}
public static final String URL_KEY = "URL_KEY";
public static final String NOTE_KEY = "NOTE_KEY";
/**
* When using Type.CONTACT, these arrays provide the keys for adding or retrieving multiple
* phone numbers and addresses.
*/
public static final String[] PHONE_KEYS = {
ContactsContract.Intents.Insert.PHONE,
ContactsContract.Intents.Insert.SECONDARY_PHONE,
ContactsContract.Intents.Insert.TERTIARY_PHONE
};
public static final String[] PHONE_TYPE_KEYS = {
ContactsContract.Intents.Insert.PHONE_TYPE,
ContactsContract.Intents.Insert.SECONDARY_PHONE_TYPE,
ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE
};
public static final String[] EMAIL_KEYS = {
ContactsContract.Intents.Insert.EMAIL,
ContactsContract.Intents.Insert.SECONDARY_EMAIL,
ContactsContract.Intents.Insert.TERTIARY_EMAIL
};
public static final String[] EMAIL_TYPE_KEYS = {
ContactsContract.Intents.Insert.EMAIL_TYPE,
ContactsContract.Intents.Insert.SECONDARY_EMAIL_TYPE,
ContactsContract.Intents.Insert.TERTIARY_EMAIL_TYPE
};
}
================================================
FILE: scanner/src/main/java/com/nanchen/scanner/zxing/DecodeFormatManager.java
================================================
/*
* Copyright (C) 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.nanchen.scanner.zxing;
import android.content.Intent;
import android.net.Uri;
import android.util.ArraySet;
import com.google.zxing.BarcodeFormat;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public final class DecodeFormatManager {
private static final Pattern COMMA_PATTERN = Pattern.compile(",");
static final Set