From 75eff5aa7fc410df03548bc30d5f75e18fd19267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 3 Feb 2021 22:14:57 +0800 Subject: [PATCH] QR related improves --- TMessagesProj/build.gradle | 18 +- .../java/com/google/zxing/BarcodeFormat.java | 27 + .../main/java/com/google/zxing/Binarizer.java | 87 +++ .../java/com/google/zxing/BinaryBitmap.java | 150 ++++ .../com/google/zxing/ChecksumException.java | 47 ++ .../java/com/google/zxing/DecodeHintType.java | 122 ++++ .../main/java/com/google/zxing/Dimension.java | 62 ++ .../java/com/google/zxing/EncodeHintType.java | 118 +++ .../com/google/zxing/FormatException.java | 47 ++ .../google/zxing/InvertedLuminanceSource.java | 88 +++ .../com/google/zxing/LuminanceSource.java | 157 ++++ .../com/google/zxing/NotFoundException.java | 40 ++ .../zxing/PlanarYUVLuminanceSource.java | 168 +++++ .../com/google/zxing/RGBLuminanceSource.java | 136 ++++ .../main/java/com/google/zxing/Reader.java | 69 ++ .../com/google/zxing/ReaderException.java | 47 ++ .../main/java/com/google/zxing/Result.java | 153 ++++ .../com/google/zxing/ResultMetadataType.java | 97 +++ .../java/com/google/zxing/ResultPoint.java | 130 ++++ .../com/google/zxing/ResultPointCallback.java | 29 + .../com/google/zxing/WriterException.java | 38 + .../com/google/zxing/common/BitArray.java | 357 +++++++++ .../com/google/zxing/common/BitMatrix.java | 488 +++++++++++++ .../com/google/zxing/common/BitSource.java | 111 +++ .../google/zxing/common/CharacterSetECI.java | 118 +++ .../google/zxing/common/DecoderResult.java | 152 ++++ .../zxing/common/DefaultGridSampler.java | 88 +++ .../google/zxing/common/DetectorResult.java | 46 ++ .../common/GlobalHistogramBinarizer.java | 203 ++++++ .../com/google/zxing/common/GridSampler.java | 174 +++++ .../google/zxing/common/HybridBinarizer.java | 237 ++++++ .../zxing/common/PerspectiveTransform.java | 156 ++++ .../com/google/zxing/common/StringUtils.java | 197 +++++ .../zxing/common/detector/MathUtils.java | 78 ++ .../detector/MonochromeRectangleDetector.java | 217 ++++++ .../detector/WhiteRectangleDetector.java | 325 +++++++++ .../zxing/common/reedsolomon/GenericGF.java | 166 +++++ .../common/reedsolomon/GenericGFPoly.java | 271 +++++++ .../reedsolomon/ReedSolomonDecoder.java | 190 +++++ .../reedsolomon/ReedSolomonEncoder.java | 74 ++ .../reedsolomon/ReedSolomonException.java | 31 + .../google/zxing/multi/ByQuadrantReader.java | 117 +++ .../multi/GenericMultipleBarcodeReader.java | 182 +++++ .../zxing/multi/MultipleBarcodeReader.java | 39 + .../zxing/multi/qrcode/QRCodeMultiReader.java | 149 ++++ .../multi/qrcode/detector/MultiDetector.java | 73 ++ .../detector/MultiFinderPatternFinder.java | 285 ++++++++ .../com/google/zxing/qrcode/QRCodeReader.java | 220 ++++++ .../com/google/zxing/qrcode/QRCodeWriter.java | 246 +++++++ .../zxing/qrcode/decoder/BitMatrixParser.java | 245 +++++++ .../zxing/qrcode/decoder/DataBlock.java | 122 ++++ .../google/zxing/qrcode/decoder/DataMask.java | 141 ++++ .../decoder/DecodedBitStreamParser.java | 360 ++++++++++ .../google/zxing/qrcode/decoder/Decoder.java | 189 +++++ .../qrcode/decoder/ErrorCorrectionLevel.java | 60 ++ .../qrcode/decoder/FormatInformation.java | 157 ++++ .../com/google/zxing/qrcode/decoder/Mode.java | 102 +++ .../qrcode/decoder/QRCodeDecoderMetaData.java | 57 ++ .../google/zxing/qrcode/decoder/Version.java | 577 +++++++++++++++ .../qrcode/detector/AlignmentPattern.java | 59 ++ .../detector/AlignmentPatternFinder.java | 277 +++++++ .../zxing/qrcode/detector/Detector.java | 406 +++++++++++ .../zxing/qrcode/detector/FinderPattern.java | 82 +++ .../qrcode/detector/FinderPatternFinder.java | 675 ++++++++++++++++++ .../qrcode/detector/FinderPatternInfo.java | 49 ++ .../zxing/qrcode/encoder/BlockPair.java | 37 + .../zxing/qrcode/encoder/ByteMatrix.java | 99 +++ .../google/zxing/qrcode/encoder/Encoder.java | 630 ++++++++++++++++ .../google/zxing/qrcode/encoder/MaskUtil.java | 222 ++++++ .../zxing/qrcode/encoder/MatrixUtil.java | 476 ++++++++++++ .../google/zxing/qrcode/encoder/QRCode.java | 108 +++ .../telegram/messenger/AndroidUtilities.java | 2 + .../java/org/telegram/ui/ArticleViewer.java | 90 +-- .../org/telegram/ui/CameraScanActivity.java | 9 +- .../telegram/ui/ChannelAdminLogActivity.java | 45 +- .../java/org/telegram/ui/ChatActivity.java | 73 +- .../org/telegram/ui/ChatUsersActivity.java | 2 +- .../ui/Components/ChatActivityEnterView.java | 2 +- .../ui/Components/QRCodeBottomSheet.java | 5 +- .../ui/Components/SharedMediaLayout.java | 43 +- .../telegram/ui/Components/StickersAlert.java | 13 + .../org/telegram/ui/FilteredSearchView.java | 43 +- .../java/org/telegram/ui/MediaActivity.java | 43 +- .../java/org/telegram/ui/ProfileActivity.java | 74 +- .../org/telegram/ui/ProxyListActivity.java | 2 +- .../tw/nekomimi/nekogram/BottomBuilder.kt | 5 +- .../tw/nekomimi/nekogram/utils/ProxyUtil.kt | 83 +-- .../drawable/baseline_content_paste_24.xml | 9 + .../drawable/baseline_keyboard_voice_24.xml | 9 + 89 files changed, 12290 insertions(+), 212 deletions(-) create mode 100755 TMessagesProj/src/main/java/com/google/zxing/BarcodeFormat.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/Binarizer.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/BinaryBitmap.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/ChecksumException.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/DecodeHintType.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/Dimension.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/EncodeHintType.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/FormatException.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/InvertedLuminanceSource.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/LuminanceSource.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/NotFoundException.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/PlanarYUVLuminanceSource.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/RGBLuminanceSource.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/Reader.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/ReaderException.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/Result.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/ResultMetadataType.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/ResultPoint.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/ResultPointCallback.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/WriterException.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/BitArray.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/BitMatrix.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/BitSource.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/CharacterSetECI.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/DecoderResult.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/DefaultGridSampler.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/DetectorResult.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/GlobalHistogramBinarizer.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/GridSampler.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/HybridBinarizer.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/PerspectiveTransform.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/StringUtils.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/detector/MathUtils.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/detector/MonochromeRectangleDetector.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/detector/WhiteRectangleDetector.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/GenericGF.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/GenericGFPoly.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonException.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/multi/ByQuadrantReader.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/multi/GenericMultipleBarcodeReader.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/multi/MultipleBarcodeReader.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/QRCodeMultiReader.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/detector/MultiDetector.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/QRCodeReader.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/QRCodeWriter.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/BitMatrixParser.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DataBlock.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Decoder.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/FormatInformation.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Mode.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Version.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/AlignmentPattern.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/Detector.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPattern.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPatternInfo.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/BlockPair.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/ByteMatrix.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/MatrixUtil.java create mode 100755 TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/QRCode.java create mode 100644 TMessagesProj/src/main/res/drawable/baseline_content_paste_24.xml create mode 100644 TMessagesProj/src/main/res/drawable/baseline_keyboard_voice_24.xml diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index d797076f6..4e035cc53 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -1,8 +1,8 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -def verName = "7.4.2-preview-6" -def verCode = 174 +def verName = "7.4.2-rc01" +def verCode = 176 def officialVer = "7.4.2" def officialCode = 2227 @@ -64,11 +64,7 @@ dependencies { implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.sharetarget:sharetarget:1.1.0' - // replace zxing with latest - // TODO: fix problem with android L - implementation 'com.google.zxing:core:3.4.1' - - compileOnly 'org.checkerframework:checker-qual:3.9.1' + compileOnly 'org.checkerframework:checker-qual:3.10.0' compileOnly 'org.checkerframework:checker-compat-qual:2.5.5' // don't change this :) @@ -85,9 +81,9 @@ dependencies { implementation 'dnsjava:dnsjava:3.3.1' implementation 'org.dizitart:nitrite:3.4.3' - implementation 'cn.hutool:hutool-core:5.5.7' - implementation 'cn.hutool:hutool-crypto:5.5.7' - implementation 'cn.hutool:hutool-http:5.5.7' + implementation 'cn.hutool:hutool-core:5.5.8' + implementation 'cn.hutool:hutool-crypto:5.5.8' + implementation 'cn.hutool:hutool-http:5.5.8' implementation 'com.jakewharton:process-phoenix:2.0.0' @@ -107,7 +103,7 @@ dependencies { testImplementation 'junit:junit:4.13.1' testImplementation 'androidx.test:core:1.3.0' - testImplementation 'org.robolectric:robolectric:4.5' + testImplementation 'org.robolectric:robolectric:4.5.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' diff --git a/TMessagesProj/src/main/java/com/google/zxing/BarcodeFormat.java b/TMessagesProj/src/main/java/com/google/zxing/BarcodeFormat.java new file mode 100755 index 000000000..a7ca3ec4a --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/BarcodeFormat.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * Enumerates barcode formats known to this package. Please keep alphabetized. + * + * @author Sean Owen + */ +public enum BarcodeFormat { + /** QR Code 2D barcode format. */ + QR_CODE +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/Binarizer.java b/TMessagesProj/src/main/java/com/google/zxing/Binarizer.java new file mode 100755 index 000000000..02af0832f --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/Binarizer.java @@ -0,0 +1,87 @@ +/* + * 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; + +import com.google.zxing.common.BitArray; +import com.google.zxing.common.BitMatrix; + +/** + * This class hierarchy provides a set of methods to convert luminance data to 1 bit data. + * It allows the algorithm to vary polymorphically, for example allowing a very expensive + * thresholding technique for servers and a fast one for mobile. It also permits the implementation + * to vary, e.g. a JNI version for Android and a Java fallback version for other platforms. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public abstract class Binarizer { + + private final LuminanceSource source; + + protected Binarizer(LuminanceSource source) { + this.source = source; + } + + public final LuminanceSource getLuminanceSource() { + return source; + } + + /** + * Converts one row of luminance data to 1 bit data. May actually do the conversion, or return + * cached data. Callers should assume this method is expensive and call it as seldom as possible. + * This method is intended for decoding 1D barcodes and may choose to apply sharpening. + * For callers which only examine one row of pixels at a time, the same BitArray should be reused + * and passed in with each call for performance. However it is legal to keep more than one row + * at a time if needed. + * + * @param y The row to fetch, which must be in [0, bitmap height) + * @param row An optional preallocated array. If null or too small, it will be ignored. + * If used, the Binarizer will call BitArray.clear(). Always use the returned object. + * @return The array of bits for this row (true means black). + * @throws NotFoundException if row can't be binarized + */ + public abstract BitArray getBlackRow(int y, BitArray row) throws NotFoundException; + + /** + * Converts a 2D array of luminance data to 1 bit data. As above, assume this method is expensive + * and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or + * may not apply sharpening. Therefore, a row from this matrix may not be identical to one + * fetched using getBlackRow(), so don't mix and match between them. + * + * @return The 2D array of bits for the image (true means black). + * @throws NotFoundException if image can't be binarized to make a matrix + */ + public abstract BitMatrix getBlackMatrix() throws NotFoundException; + + /** + * Creates a new object with the same type as this Binarizer implementation, but with pristine + * state. This is needed because Binarizer implementations may be stateful, e.g. keeping a cache + * of 1 bit data. See Effective Java for why we can't use Java's clone() method. + * + * @param source The LuminanceSource this Binarizer will operate on. + * @return A new concrete Binarizer implementation object. + */ + public abstract Binarizer createBinarizer(LuminanceSource source); + + public final int getWidth() { + return source.getWidth(); + } + + public final int getHeight() { + return source.getHeight(); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/BinaryBitmap.java b/TMessagesProj/src/main/java/com/google/zxing/BinaryBitmap.java new file mode 100755 index 000000000..c1ef8a13e --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/BinaryBitmap.java @@ -0,0 +1,150 @@ +/* + * 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; + +import com.google.zxing.common.BitArray; +import com.google.zxing.common.BitMatrix; + +/** + * This class is the core bitmap class used by ZXing to represent 1 bit data. Reader objects + * accept a BinaryBitmap and attempt to decode it. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class BinaryBitmap { + + private final Binarizer binarizer; + private BitMatrix matrix; + + public BinaryBitmap(Binarizer binarizer) { + if (binarizer == null) { + throw new IllegalArgumentException("Binarizer must be non-null."); + } + this.binarizer = binarizer; + } + + /** + * @return The width of the bitmap. + */ + public int getWidth() { + return binarizer.getWidth(); + } + + /** + * @return The height of the bitmap. + */ + public int getHeight() { + return binarizer.getHeight(); + } + + /** + * Converts one row of luminance data to 1 bit data. May actually do the conversion, or return + * cached data. Callers should assume this method is expensive and call it as seldom as possible. + * This method is intended for decoding 1D barcodes and may choose to apply sharpening. + * + * @param y The row to fetch, which must be in [0, bitmap height) + * @param row An optional preallocated array. If null or too small, it will be ignored. + * If used, the Binarizer will call BitArray.clear(). Always use the returned object. + * @return The array of bits for this row (true means black). + * @throws NotFoundException if row can't be binarized + */ + public BitArray getBlackRow(int y, BitArray row) throws NotFoundException { + return binarizer.getBlackRow(y, row); + } + + /** + * Converts a 2D array of luminance data to 1 bit. As above, assume this method is expensive + * and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or + * may not apply sharpening. Therefore, a row from this matrix may not be identical to one + * fetched using getBlackRow(), so don't mix and match between them. + * + * @return The 2D array of bits for the image (true means black). + * @throws NotFoundException if image can't be binarized to make a matrix + */ + public BitMatrix getBlackMatrix() throws NotFoundException { + // The matrix is created on demand the first time it is requested, then cached. There are two + // reasons for this: + // 1. This work will never be done if the caller only installs 1D Reader objects, or if a + // 1D Reader finds a barcode before the 2D Readers run. + // 2. This work will only be done once even if the caller installs multiple 2D Readers. + if (matrix == null) { + matrix = binarizer.getBlackMatrix(); + } + return matrix; + } + + /** + * @return Whether this bitmap can be cropped. + */ + public boolean isCropSupported() { + return binarizer.getLuminanceSource().isCropSupported(); + } + + /** + * Returns a new object with cropped image data. Implementations may keep a reference to the + * original data rather than a copy. Only callable if isCropSupported() is true. + * + * @param left The left coordinate, which must be in [0,getWidth()) + * @param top The top coordinate, which must be in [0,getHeight()) + * @param width The width of the rectangle to crop. + * @param height The height of the rectangle to crop. + * @return A cropped version of this object. + */ + public BinaryBitmap crop(int left, int top, int width, int height) { + LuminanceSource newSource = binarizer.getLuminanceSource().crop(left, top, width, height); + return new BinaryBitmap(binarizer.createBinarizer(newSource)); + } + + /** + * @return Whether this bitmap supports counter-clockwise rotation. + */ + public boolean isRotateSupported() { + return binarizer.getLuminanceSource().isRotateSupported(); + } + + /** + * Returns a new object with rotated image data by 90 degrees counterclockwise. + * Only callable if {@link #isRotateSupported()} is true. + * + * @return A rotated version of this object. + */ + public BinaryBitmap rotateCounterClockwise() { + LuminanceSource newSource = binarizer.getLuminanceSource().rotateCounterClockwise(); + return new BinaryBitmap(binarizer.createBinarizer(newSource)); + } + + /** + * Returns a new object with rotated image data by 45 degrees counterclockwise. + * Only callable if {@link #isRotateSupported()} is true. + * + * @return A rotated version of this object. + */ + public BinaryBitmap rotateCounterClockwise45() { + LuminanceSource newSource = binarizer.getLuminanceSource().rotateCounterClockwise45(); + return new BinaryBitmap(binarizer.createBinarizer(newSource)); + } + + @Override + public String toString() { + try { + return getBlackMatrix().toString(); + } catch (NotFoundException e) { + return ""; + } + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/ChecksumException.java b/TMessagesProj/src/main/java/com/google/zxing/ChecksumException.java new file mode 100755 index 000000000..c5acbe3ee --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/ChecksumException.java @@ -0,0 +1,47 @@ +/* + * 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; + +/** + * Thrown when a barcode was successfully detected and decoded, but + * was not returned because its checksum feature failed. + * + * @author Sean Owen + */ +public final class ChecksumException extends ReaderException { + + private static final ChecksumException INSTANCE = new ChecksumException(); + static { + INSTANCE.setStackTrace(NO_TRACE); // since it's meaningless + } + + private ChecksumException() { + // do nothing + } + + private ChecksumException(Throwable cause) { + super(cause); + } + + public static ChecksumException getChecksumInstance() { + return isStackTrace ? new ChecksumException() : INSTANCE; + } + + public static ChecksumException getChecksumInstance(Throwable cause) { + return isStackTrace ? new ChecksumException(cause) : INSTANCE; + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/com/google/zxing/DecodeHintType.java b/TMessagesProj/src/main/java/com/google/zxing/DecodeHintType.java new file mode 100755 index 000000000..11f579e62 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/DecodeHintType.java @@ -0,0 +1,122 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +import java.util.List; + +/** + * Encapsulates a type of hint that a caller may pass to a barcode reader to help it + * more quickly or accurately decode it. It is up to implementations to decide what, + * if anything, to do with the information that is supplied. + * + * @author Sean Owen + * @author dswitkin@google.com (Daniel Switkin) + * @see Reader#decode(BinaryBitmap,java.util.Map) + */ +public enum DecodeHintType { + + /** + * Unspecified, application-specific hint. Maps to an unspecified {@link Object}. + */ + OTHER(Object.class), + + /** + * Image is a pure monochrome image of a barcode. Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + PURE_BARCODE(Void.class), + + /** + * Image is known to be of one of a few possible formats. + * Maps to a {@link List} of {@link BarcodeFormat}s. + */ + POSSIBLE_FORMATS(List.class), + + /** + * Spend more time to try to find a barcode; optimize for accuracy, not speed. + * Doesn't matter what it maps to; use {@link Boolean#TRUE}. + */ + TRY_HARDER(Void.class), + + /** + * Specifies what character encoding to use when decoding, where applicable (type String) + */ + CHARACTER_SET(String.class), + + /** + * Allowed lengths of encoded data -- reject anything else. Maps to an {@code int[]}. + */ + ALLOWED_LENGTHS(int[].class), + + /** + * Assume Code 39 codes employ a check digit. Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + ASSUME_CODE_39_CHECK_DIGIT(Void.class), + + /** + * Assume the barcode is being processed as a GS1 barcode, and modify behavior as needed. + * For example this affects FNC1 handling for Code 128 (aka GS1-128). Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + ASSUME_GS1(Void.class), + + /** + * If true, return the start and end digits in a Codabar barcode instead of stripping them. They + * are alpha, whereas the rest are numeric. By default, they are stripped, but this causes them + * to not be. Doesn't matter what it maps to; use {@link Boolean#TRUE}. + */ + RETURN_CODABAR_START_END(Void.class), + + /** + * The caller needs to be notified via callback when a possible {@link ResultPoint} + * is found. Maps to a {@link ResultPointCallback}. + */ + NEED_RESULT_POINT_CALLBACK(ResultPointCallback.class), + + + /** + * Allowed extension lengths for EAN or UPC barcodes. Other formats will ignore this. + * Maps to an {@code int[]} of the allowed extension lengths, for example [2], [5], or [2, 5]. + * If it is optional to have an extension, do not set this hint. If this is set, + * and a UPC or EAN barcode is found but an extension is not, then no result will be returned + * at all. + */ + ALLOWED_EAN_EXTENSIONS(int[].class), + + // End of enumeration values. + ; + + /** + * Data type the hint is expecting. + * Among the possible values the {@link Void} stands out as being used for + * hints that do not expect a value to be supplied (flag hints). Such hints + * will possibly have their value ignored, or replaced by a + * {@link Boolean#TRUE}. Hint suppliers should probably use + * {@link Boolean#TRUE} as directed by the actual hint documentation. + */ + private final Class valueType; + + DecodeHintType(Class valueType) { + this.valueType = valueType; + } + + public Class getValueType() { + return valueType; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/Dimension.java b/TMessagesProj/src/main/java/com/google/zxing/Dimension.java new file mode 100755 index 000000000..dd40d06d0 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/Dimension.java @@ -0,0 +1,62 @@ +/* + * 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; + +/** + * Simply encapsulates a width and height. + */ +public final class Dimension { + + private final int width; + private final int height; + + public Dimension(int width, int height) { + if (width < 0 || height < 0) { + throw new IllegalArgumentException(); + } + this.width = width; + this.height = height; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + @Override + public boolean equals(Object other) { + if (other instanceof Dimension) { + Dimension d = (Dimension) other; + return width == d.width && height == d.height; + } + return false; + } + + @Override + public int hashCode() { + return width * 32713 + height; + } + + @Override + public String toString() { + return width + "x" + height; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/EncodeHintType.java b/TMessagesProj/src/main/java/com/google/zxing/EncodeHintType.java new file mode 100755 index 000000000..9a2fd57e6 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/EncodeHintType.java @@ -0,0 +1,118 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +/** + * These are a set of hints that you may pass to Writers to specify their behavior. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public enum EncodeHintType { + + /** + * Specifies what degree of error correction to use, for example in QR Codes. + * Type depends on the encoder. For example for QR codes it's type + * {@link com.google.zxing.qrcode.decoder.ErrorCorrectionLevel ErrorCorrectionLevel}. + * For Aztec it is of type {@link Integer}, representing the minimal percentage of error correction words. + * For PDF417 it is of type {@link Integer}, valid values being 0 to 8. + * In all cases, it can also be a {@link String} representation of the desired value as well. + * Note: an Aztec symbol should have a minimum of 25% EC words. + */ + ERROR_CORRECTION, + + /** + * Specifies what character encoding to use where applicable (type {@link String}) + */ + CHARACTER_SET, + + /** + * Specifies the matrix shape for Data Matrix (type {@link com.google.zxing.datamatrix.encoder.SymbolShapeHint}) + */ + DATA_MATRIX_SHAPE, + + /** + * Specifies a minimum barcode size (type {@link Dimension}). Only applicable to Data Matrix now. + * + * @deprecated use width/height params in + * {@link com.google.zxing.datamatrix.DataMatrixWriter#encode(String, BarcodeFormat, int, int)} + */ + @Deprecated + MIN_SIZE, + + /** + * Specifies a maximum barcode size (type {@link Dimension}). Only applicable to Data Matrix now. + * + * @deprecated without replacement + */ + @Deprecated + MAX_SIZE, + + /** + * Specifies margin, in pixels, to use when generating the barcode. The meaning can vary + * by format; for example it controls margin before and after the barcode horizontally for + * most 1D formats. (Type {@link Integer}, or {@link String} representation of the integer value). + */ + MARGIN, + + /** + * Specifies whether to use compact mode for PDF417 (type {@link Boolean}, or "true" or "false" + * {@link String} value). + */ + PDF417_COMPACT, + + /** + * Specifies what compaction mode to use for PDF417 (type + * {@link com.google.zxing.pdf417.encoder.Compaction Compaction} or {@link String} value of one of its + * enum values). + */ + PDF417_COMPACTION, + + /** + * Specifies the minimum and maximum number of rows and columns for PDF417 (type + * {@link com.google.zxing.pdf417.encoder.Dimensions Dimensions}). + */ + PDF417_DIMENSIONS, + + /** + * Specifies the required number of layers for an Aztec code. + * A negative number (-1, -2, -3, -4) specifies a compact Aztec code. + * 0 indicates to use the minimum number of layers (the default). + * A positive number (1, 2, .. 32) specifies a normal (non-compact) Aztec code. + * (Type {@link Integer}, or {@link String} representation of the integer value). + */ + AZTEC_LAYERS, + + /** + * Specifies the exact version of QR code to be encoded. + * (Type {@link Integer}, or {@link String} representation of the integer value). + */ + QR_VERSION, + + /** + * Specifies the QR code mask pattern to be used. Allowed values are + * 0..QRCode.NUM_MASK_PATTERNS-1. By default the code will automatically select + * the optimal mask pattern. + * * (Type {@link Integer}, or {@link String} representation of the integer value). + */ + QR_MASK_PATTERN, + + /** + * Specifies whether the data should be encoded to the GS1 standard (type {@link Boolean}, or "true" or "false" + * {@link String } value). + */ + GS1_FORMAT, +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/FormatException.java b/TMessagesProj/src/main/java/com/google/zxing/FormatException.java new file mode 100755 index 000000000..ebd800862 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/FormatException.java @@ -0,0 +1,47 @@ +/* + * 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; + +/** + * Thrown when a barcode was successfully detected, but some aspect of + * the content did not conform to the barcode's format rules. This could have + * been due to a mis-detection. + * + * @author Sean Owen + */ +public final class FormatException extends ReaderException { + + private static final FormatException INSTANCE = new FormatException(); + static { + INSTANCE.setStackTrace(NO_TRACE); // since it's meaningless + } + + private FormatException() { + } + + private FormatException(Throwable cause) { + super(cause); + } + + public static FormatException getFormatInstance() { + return isStackTrace ? new FormatException() : INSTANCE; + } + + public static FormatException getFormatInstance(Throwable cause) { + return isStackTrace ? new FormatException(cause) : INSTANCE; + } +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/InvertedLuminanceSource.java b/TMessagesProj/src/main/java/com/google/zxing/InvertedLuminanceSource.java new file mode 100755 index 000000000..a64e15d60 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/InvertedLuminanceSource.java @@ -0,0 +1,88 @@ +/* + * 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; + +/** + * A wrapper implementation of {@link LuminanceSource} which inverts the luminances it returns -- black becomes + * white and vice versa, and each value becomes (255-value). + * + * @author Sean Owen + */ +public final class InvertedLuminanceSource extends LuminanceSource { + + private final LuminanceSource delegate; + + public InvertedLuminanceSource(LuminanceSource delegate) { + super(delegate.getWidth(), delegate.getHeight()); + this.delegate = delegate; + } + + @Override + public byte[] getRow(int y, byte[] row) { + row = delegate.getRow(y, row); + int width = getWidth(); + for (int i = 0; i < width; i++) { + row[i] = (byte) (255 - (row[i] & 0xFF)); + } + return row; + } + + @Override + public byte[] getMatrix() { + byte[] matrix = delegate.getMatrix(); + int length = getWidth() * getHeight(); + byte[] invertedMatrix = new byte[length]; + for (int i = 0; i < length; i++) { + invertedMatrix[i] = (byte) (255 - (matrix[i] & 0xFF)); + } + return invertedMatrix; + } + + @Override + public boolean isCropSupported() { + return delegate.isCropSupported(); + } + + @Override + public LuminanceSource crop(int left, int top, int width, int height) { + return new InvertedLuminanceSource(delegate.crop(left, top, width, height)); + } + + @Override + public boolean isRotateSupported() { + return delegate.isRotateSupported(); + } + + /** + * @return original delegate {@link LuminanceSource} since invert undoes itself + */ + @Override + public LuminanceSource invert() { + return delegate; + } + + @Override + public LuminanceSource rotateCounterClockwise() { + return new InvertedLuminanceSource(delegate.rotateCounterClockwise()); + } + + @Override + public LuminanceSource rotateCounterClockwise45() { + return new InvertedLuminanceSource(delegate.rotateCounterClockwise45()); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/LuminanceSource.java b/TMessagesProj/src/main/java/com/google/zxing/LuminanceSource.java new file mode 100755 index 000000000..1946d023c --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/LuminanceSource.java @@ -0,0 +1,157 @@ +/* + * 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; + +/** + * The purpose of this class hierarchy is to abstract different bitmap implementations across + * platforms into a standard interface for requesting greyscale luminance values. The interface + * only provides immutable methods; therefore crop and rotation create copies. This is to ensure + * that one Reader does not modify the original luminance source and leave it in an unknown state + * for other Readers in the chain. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public abstract class LuminanceSource { + + private final int width; + private final int height; + + protected LuminanceSource(int width, int height) { + this.width = width; + this.height = height; + } + + /** + * Fetches one row of luminance data from the underlying platform's bitmap. Values range from + * 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have + * to bitwise and with 0xff for each value. It is preferable for implementations of this method + * to only fetch this row rather than the whole image, since no 2D Readers may be installed and + * getMatrix() may never be called. + * + * @param y The row to fetch, which must be in [0,getHeight()) + * @param row An optional preallocated array. If null or too small, it will be ignored. + * Always use the returned object, and ignore the .length of the array. + * @return An array containing the luminance data. + */ + public abstract byte[] getRow(int y, byte[] row); + + /** + * Fetches luminance data for the underlying bitmap. Values should be fetched using: + * {@code int luminance = array[y * width + x] & 0xff} + * + * @return A row-major 2D array of luminance values. Do not use result.length as it may be + * larger than width * height bytes on some platforms. Do not modify the contents + * of the result. + */ + public abstract byte[] getMatrix(); + + /** + * @return The width of the bitmap. + */ + public final int getWidth() { + return width; + } + + /** + * @return The height of the bitmap. + */ + public final int getHeight() { + return height; + } + + /** + * @return Whether this subclass supports cropping. + */ + public boolean isCropSupported() { + return false; + } + + /** + * Returns a new object with cropped image data. Implementations may keep a reference to the + * original data rather than a copy. Only callable if isCropSupported() is true. + * + * @param left The left coordinate, which must be in [0,getWidth()) + * @param top The top coordinate, which must be in [0,getHeight()) + * @param width The width of the rectangle to crop. + * @param height The height of the rectangle to crop. + * @return A cropped version of this object. + */ + public LuminanceSource crop(int left, int top, int width, int height) { + throw new UnsupportedOperationException("This luminance source does not support cropping."); + } + + /** + * @return Whether this subclass supports counter-clockwise rotation. + */ + public boolean isRotateSupported() { + return false; + } + + /** + * @return a wrapper of this {@code LuminanceSource} which inverts the luminances it returns -- black becomes + * white and vice versa, and each value becomes (255-value). + */ + public LuminanceSource invert() { + return new InvertedLuminanceSource(this); + } + + /** + * Returns a new object with rotated image data by 90 degrees counterclockwise. + * Only callable if {@link #isRotateSupported()} is true. + * + * @return A rotated version of this object. + */ + public LuminanceSource rotateCounterClockwise() { + throw new UnsupportedOperationException("This luminance source does not support rotation by 90 degrees."); + } + + /** + * Returns a new object with rotated image data by 45 degrees counterclockwise. + * Only callable if {@link #isRotateSupported()} is true. + * + * @return A rotated version of this object. + */ + public LuminanceSource rotateCounterClockwise45() { + throw new UnsupportedOperationException("This luminance source does not support rotation by 45 degrees."); + } + + @Override + public final String toString() { + byte[] row = new byte[width]; + StringBuilder result = new StringBuilder(height * (width + 1)); + for (int y = 0; y < height; y++) { + row = getRow(y, row); + for (int x = 0; x < width; x++) { + int luminance = row[x] & 0xFF; + char c; + if (luminance < 0x40) { + c = '#'; + } else if (luminance < 0x80) { + c = '+'; + } else if (luminance < 0xC0) { + c = '.'; + } else { + c = ' '; + } + result.append(c); + } + result.append('\n'); + } + return result.toString(); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/NotFoundException.java b/TMessagesProj/src/main/java/com/google/zxing/NotFoundException.java new file mode 100755 index 000000000..863526af4 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/NotFoundException.java @@ -0,0 +1,40 @@ +/* + * 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; + +/** + * Thrown when a barcode was not found in the image. It might have been + * partially detected but could not be confirmed. + * + * @author Sean Owen + */ +public final class NotFoundException extends ReaderException { + + private static final NotFoundException INSTANCE = new NotFoundException(); + static { + INSTANCE.setStackTrace(NO_TRACE); // since it's meaningless + } + + private NotFoundException() { + // do nothing + } + + public static NotFoundException getNotFoundInstance() { + return INSTANCE; + } + +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/com/google/zxing/PlanarYUVLuminanceSource.java b/TMessagesProj/src/main/java/com/google/zxing/PlanarYUVLuminanceSource.java new file mode 100755 index 000000000..cecff3efa --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/PlanarYUVLuminanceSource.java @@ -0,0 +1,168 @@ +/* + * 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; + +/** + * This object extends LuminanceSource around an array of YUV data returned from the camera driver, + * with the option to crop to a rectangle within the full data. This can be used to exclude + * superfluous pixels around the perimeter and speed up decoding. + * + * It works for any pixel format where the Y channel is planar and appears first, including + * YCbCr_420_SP and YCbCr_422_SP. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class PlanarYUVLuminanceSource extends LuminanceSource { + + private static final int THUMBNAIL_SCALE_FACTOR = 2; + + private final byte[] yuvData; + private final int dataWidth; + private final int dataHeight; + private final int left; + private final int top; + + public PlanarYUVLuminanceSource(byte[] yuvData, + int dataWidth, + int dataHeight, + int left, + int top, + int width, + int height, + boolean reverseHorizontal) { + super(width, height); + + if (left + width > dataWidth || top + height > dataHeight) { + throw new IllegalArgumentException("Crop rectangle does not fit within image data."); + } + + this.yuvData = yuvData; + this.dataWidth = dataWidth; + this.dataHeight = dataHeight; + this.left = left; + this.top = top; + if (reverseHorizontal) { + reverseHorizontal(width, height); + } + } + + @Override + public byte[] getRow(int y, byte[] row) { + if (y < 0 || y >= getHeight()) { + throw new IllegalArgumentException("Requested row is outside the image: " + y); + } + int width = getWidth(); + if (row == null || row.length < width) { + row = new byte[width]; + } + int offset = (y + top) * dataWidth + left; + System.arraycopy(yuvData, offset, row, 0, width); + return row; + } + + @Override + public byte[] getMatrix() { + int width = getWidth(); + int height = getHeight(); + + // If the caller asks for the entire underlying image, save the copy and give them the + // original data. The docs specifically warn that result.length must be ignored. + if (width == dataWidth && height == dataHeight) { + return yuvData; + } + + int area = width * height; + byte[] matrix = new byte[area]; + int inputOffset = top * dataWidth + left; + + // If the width matches the full width of the underlying data, perform a single copy. + if (width == dataWidth) { + System.arraycopy(yuvData, inputOffset, matrix, 0, area); + return matrix; + } + + // Otherwise copy one cropped row at a time. + for (int y = 0; y < height; y++) { + int outputOffset = y * width; + System.arraycopy(yuvData, inputOffset, matrix, outputOffset, width); + inputOffset += dataWidth; + } + return matrix; + } + + @Override + public boolean isCropSupported() { + return true; + } + + @Override + public LuminanceSource crop(int left, int top, int width, int height) { + return new PlanarYUVLuminanceSource(yuvData, + dataWidth, + dataHeight, + this.left + left, + this.top + top, + width, + height, + false); + } + + public int[] renderThumbnail() { + int width = getWidth() / THUMBNAIL_SCALE_FACTOR; + int height = getHeight() / THUMBNAIL_SCALE_FACTOR; + int[] pixels = new int[width * height]; + byte[] yuv = yuvData; + int inputOffset = top * dataWidth + left; + + for (int y = 0; y < height; y++) { + int outputOffset = y * width; + for (int x = 0; x < width; x++) { + int grey = yuv[inputOffset + x * THUMBNAIL_SCALE_FACTOR] & 0xff; + pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101); + } + inputOffset += dataWidth * THUMBNAIL_SCALE_FACTOR; + } + return pixels; + } + + /** + * @return width of image from {@link #renderThumbnail()} + */ + public int getThumbnailWidth() { + return getWidth() / THUMBNAIL_SCALE_FACTOR; + } + + /** + * @return height of image from {@link #renderThumbnail()} + */ + public int getThumbnailHeight() { + return getHeight() / THUMBNAIL_SCALE_FACTOR; + } + + private void reverseHorizontal(int width, int height) { + byte[] yuvData = this.yuvData; + for (int y = 0, rowStart = top * dataWidth + left; y < height; y++, rowStart += dataWidth) { + int middle = rowStart + width / 2; + for (int x1 = rowStart, x2 = rowStart + width - 1; x1 < middle; x1++, x2--) { + byte temp = yuvData[x1]; + yuvData[x1] = yuvData[x2]; + yuvData[x2] = temp; + } + } + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/RGBLuminanceSource.java b/TMessagesProj/src/main/java/com/google/zxing/RGBLuminanceSource.java new file mode 100755 index 000000000..3950ce8b8 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/RGBLuminanceSource.java @@ -0,0 +1,136 @@ +/* + * 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; + +/** + * This class is used to help decode images from files which arrive as RGB data from + * an ARGB pixel array. It does not support rotation. + * + * @author dswitkin@google.com (Daniel Switkin) + * @author Betaminos + */ +public final class RGBLuminanceSource extends LuminanceSource { + + private final byte[] luminances; + private final int dataWidth; + private final int dataHeight; + private final int left; + private final int top; + + public RGBLuminanceSource(int width, int height, int[] pixels) { + super(width, height); + + dataWidth = width; + dataHeight = height; + left = 0; + top = 0; + + // In order to measure pure decoding speed, we convert the entire image to a greyscale array + // up front, which is the same as the Y channel of the YUVLuminanceSource in the real app. + // + // Total number of pixels suffices, can ignore shape + int size = width * height; + luminances = new byte[size]; + for (int offset = 0; offset < size; offset++) { + int pixel = pixels[offset]; + int r = (pixel >> 16) & 0xff; // red + int g2 = (pixel >> 7) & 0x1fe; // 2 * green + int b = pixel & 0xff; // blue + // Calculate green-favouring average cheaply + luminances[offset] = (byte) ((r + g2 + b) / 4); + } + } + + private RGBLuminanceSource(byte[] pixels, + int dataWidth, + int dataHeight, + int left, + int top, + int width, + int height) { + super(width, height); + if (left + width > dataWidth || top + height > dataHeight) { + throw new IllegalArgumentException("Crop rectangle does not fit within image data."); + } + this.luminances = pixels; + this.dataWidth = dataWidth; + this.dataHeight = dataHeight; + this.left = left; + this.top = top; + } + + @Override + public byte[] getRow(int y, byte[] row) { + if (y < 0 || y >= getHeight()) { + throw new IllegalArgumentException("Requested row is outside the image: " + y); + } + int width = getWidth(); + if (row == null || row.length < width) { + row = new byte[width]; + } + int offset = (y + top) * dataWidth + left; + System.arraycopy(luminances, offset, row, 0, width); + return row; + } + + @Override + public byte[] getMatrix() { + int width = getWidth(); + int height = getHeight(); + + // If the caller asks for the entire underlying image, save the copy and give them the + // original data. The docs specifically warn that result.length must be ignored. + if (width == dataWidth && height == dataHeight) { + return luminances; + } + + int area = width * height; + byte[] matrix = new byte[area]; + int inputOffset = top * dataWidth + left; + + // If the width matches the full width of the underlying data, perform a single copy. + if (width == dataWidth) { + System.arraycopy(luminances, inputOffset, matrix, 0, area); + return matrix; + } + + // Otherwise copy one cropped row at a time. + for (int y = 0; y < height; y++) { + int outputOffset = y * width; + System.arraycopy(luminances, inputOffset, matrix, outputOffset, width); + inputOffset += dataWidth; + } + return matrix; + } + + @Override + public boolean isCropSupported() { + return true; + } + + @Override + public LuminanceSource crop(int left, int top, int width, int height) { + return new RGBLuminanceSource(luminances, + dataWidth, + dataHeight, + this.left + left, + this.top + top, + width, + height); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/Reader.java b/TMessagesProj/src/main/java/com/google/zxing/Reader.java new file mode 100755 index 000000000..0e6af109e --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/Reader.java @@ -0,0 +1,69 @@ +/* + * 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 java.util.Map; + +/** + * Implementations of this interface can decode an image of a barcode in some format into + * the String it encodes. For example, {@link com.google.zxing.qrcode.QRCodeReader} can + * decode a QR code. The decoder may optionally receive hints from the caller which may help + * it decode more quickly or accurately. + * + * See {@link MultiFormatReader}, which attempts to determine what barcode + * format is present within the image as well, and then decodes it accordingly. + * + * @author Sean Owen + * @author dswitkin@google.com (Daniel Switkin) + */ +public interface Reader { + + /** + * Locates and decodes a barcode in some format within an image. + * + * @param image image of barcode to decode + * @return String which the barcode encodes + * @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 + */ + Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException; + + /** + * Locates and decodes a barcode in some format within an image. This method also accepts + * hints, each possibly associated to some data, which may help the implementation decode. + * + * @param image image of barcode to decode + * @param hints passed as a {@link Map} from {@link DecodeHintType} + * to arbitrary data. The + * meaning of the data depends upon the hint type. The implementation may or may not do + * anything with these hints. + * @return String which the barcode encodes + * @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 + */ + Result decode(BinaryBitmap image, Map hints) + throws NotFoundException, ChecksumException, FormatException; + + /** + * Resets any internal state the implementation has after a decode, to prepare it + * for reuse. + */ + void reset(); + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/ReaderException.java b/TMessagesProj/src/main/java/com/google/zxing/ReaderException.java new file mode 100755 index 000000000..32bbcda9a --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/ReaderException.java @@ -0,0 +1,47 @@ +/* + * 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; + +/** + * The general exception class throw when something goes wrong during decoding of a barcode. + * This includes, but is not limited to, failing checksums / error correction algorithms, being + * unable to locate finder timing patterns, and so on. + * + * @author Sean Owen + */ +public abstract class ReaderException extends Exception { + + // disable stack traces when not running inside test units + protected static final boolean isStackTrace = + System.getProperty("surefire.test.class.path") != null; + protected static final StackTraceElement[] NO_TRACE = new StackTraceElement[0]; + + ReaderException() { + // do nothing + } + + ReaderException(Throwable cause) { + super(cause); + } + + // Prevent stack traces from being taken + @Override + public final synchronized Throwable fillInStackTrace() { + return null; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/Result.java b/TMessagesProj/src/main/java/com/google/zxing/Result.java new file mode 100755 index 000000000..3df435f2a --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/Result.java @@ -0,0 +1,153 @@ +/* + * 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 java.util.EnumMap; +import java.util.Map; + +/** + *

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 Map resultMetadata; + private final long timestamp; + + public Result(String text, + byte[] rawBytes, + ResultPoint[] resultPoints, + BarcodeFormat format) { + this(text, rawBytes, resultPoints, format, System.currentTimeMillis()); + } + + public Result(String text, + byte[] rawBytes, + ResultPoint[] resultPoints, + BarcodeFormat format, + long timestamp) { + this(text, rawBytes, rawBytes == null ? 0 : 8 * rawBytes.length, + resultPoints, format, timestamp); + } + + public Result(String text, + byte[] rawBytes, + int numBits, + ResultPoint[] resultPoints, + BarcodeFormat format, + long timestamp) { + this.text = text; + this.rawBytes = rawBytes; + this.numBits = numBits; + this.resultPoints = resultPoints; + this.format = format; + this.resultMetadata = null; + this.timestamp = timestamp; + } + + /** + * @return raw text encoded by the barcode + */ + public String getText() { + return text; + } + + /** + * @return raw bytes encoded by the barcode, if applicable, otherwise {@code null} + */ + public byte[] getRawBytes() { + return rawBytes; + } + + /** + * @return how many bits of {@link #getRawBytes()} are valid; typically 8 times its length + * @since 3.3.0 + */ + public int getNumBits() { + return numBits; + } + + /** + * @return points related to the barcode in the image. These are typically points + * identifying finder patterns or the corners of the barcode. The exact meaning is + * specific to the type of barcode that was decoded. + */ + public ResultPoint[] getResultPoints() { + return resultPoints; + } + + /** + * @return {@link BarcodeFormat} representing the format of the barcode that was decoded + */ + public BarcodeFormat getBarcodeFormat() { + return format; + } + + /** + * @return {@link Map} mapping {@link ResultMetadataType} keys to values. May be + * {@code null}. This contains optional metadata about what was detected about the barcode, + * like orientation. + */ + public Map getResultMetadata() { + return resultMetadata; + } + + public void putMetadata(ResultMetadataType type, Object value) { + if (resultMetadata == null) { + resultMetadata = new EnumMap<>(ResultMetadataType.class); + } + resultMetadata.put(type, value); + } + + public void putAllMetadata(Map metadata) { + if (metadata != null) { + if (resultMetadata == null) { + resultMetadata = metadata; + } else { + resultMetadata.putAll(metadata); + } + } + } + + public void addResultPoints(ResultPoint[] newPoints) { + ResultPoint[] oldPoints = resultPoints; + if (oldPoints == null) { + resultPoints = newPoints; + } else if (newPoints != null && newPoints.length > 0) { + ResultPoint[] allPoints = new ResultPoint[oldPoints.length + newPoints.length]; + System.arraycopy(oldPoints, 0, allPoints, 0, oldPoints.length); + System.arraycopy(newPoints, 0, allPoints, oldPoints.length, newPoints.length); + resultPoints = allPoints; + } + } + + public long getTimestamp() { + return timestamp; + } + + @Override + public String toString() { + return text; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/ResultMetadataType.java b/TMessagesProj/src/main/java/com/google/zxing/ResultMetadataType.java new file mode 100755 index 000000000..67c5363a7 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/ResultMetadataType.java @@ -0,0 +1,97 @@ +/* + * 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; + +/** + * Represents some type of metadata about the result of the decoding that the decoder + * wishes to communicate back to the caller. + * + * @author Sean Owen + */ +public enum ResultMetadataType { + + /** + * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}. + */ + OTHER, + + /** + * Denotes the likely approximate orientation of the barcode in the image. This value + * is given as degrees rotated clockwise from the normal, upright orientation. + * For example a 1D barcode which was found by reading top-to-bottom would be + * said to have orientation "90". This key maps to an {@link Integer} whose + * value is in the range [0,360). + */ + ORIENTATION, + + /** + *

2D 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, + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/ResultPoint.java b/TMessagesProj/src/main/java/com/google/zxing/ResultPoint.java new file mode 100755 index 000000000..9bd8cd2e6 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/ResultPoint.java @@ -0,0 +1,130 @@ +/* + * 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)); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/ResultPointCallback.java b/TMessagesProj/src/main/java/com/google/zxing/ResultPointCallback.java new file mode 100755 index 000000000..0c85410bc --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/ResultPointCallback.java @@ -0,0 +1,29 @@ +/* + * 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); + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/WriterException.java b/TMessagesProj/src/main/java/com/google/zxing/WriterException.java new file mode 100755 index 000000000..d2a37bc52 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/WriterException.java @@ -0,0 +1,38 @@ +/* + * 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; + +/** + * A base class which covers the range of exceptions which may occur when encoding a barcode using + * the Writer framework. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class WriterException extends Exception { + + public WriterException() { + } + + public WriterException(String message) { + super(message); + } + + public WriterException(Throwable cause) { + super(cause); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/BitArray.java b/TMessagesProj/src/main/java/com/google/zxing/common/BitArray.java new file mode 100755 index 000000000..19f0c99fd --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/BitArray.java @@ -0,0 +1,357 @@ +/* + * 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 Math.min(result, size); + } + + /** + * @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 Math.min(result, size); + } + + /** + * 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); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/BitMatrix.java b/TMessagesProj/src/main/java/com/google/zxing/common/BitMatrix.java new file mode 100755 index 000000000..fa38120e0 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/BitMatrix.java @@ -0,0 +1,488 @@ +/* + * 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; + private final int multiple; + + /** + * Creates an empty square {@code BitMatrix}. + * + * @param dimension height and width + */ + public BitMatrix(int dimension) { + this(dimension, dimension, 1); + } + + /** + * Creates an empty {@code BitMatrix}. + * + * @param width bit matrix width + * @param height bit matrix height + */ + public BitMatrix(int width, int height, int multiple) { + if (width < 1 || height < 1) { + throw new IllegalArgumentException("Both dimensions must be greater than 0"); + } + this.width = width; + this.height = height; + this.multiple = multiple; + 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; + this.multiple = 1; + } + + /** + * 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, 1); + 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, 1); + 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; + } + + public int getMultiple() { + return multiple; + } + + /** + * @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()); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/BitSource.java b/TMessagesProj/src/main/java/com/google/zxing/common/BitSource.java new file mode 100755 index 000000000..b4d0aafbf --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/BitSource.java @@ -0,0 +1,111 @@ +/* + * 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 = Math.min(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; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/CharacterSetECI.java b/TMessagesProj/src/main/java/com/google/zxing/common/CharacterSetECI.java new file mode 100755 index 000000000..4be90c234 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/CharacterSetECI.java @@ -0,0 +1,118 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.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 Map VALUE_TO_ECI = new HashMap<>(); + private static final Map NAME_TO_ECI = new HashMap<>(); + static { + for (CharacterSetECI eci : values()) { + for (int value : eci.values) { + VALUE_TO_ECI.put(value, eci); + } + NAME_TO_ECI.put(eci.name(), eci); + for (String name : eci.otherEncodingNames) { + NAME_TO_ECI.put(name, eci); + } + } + } + + private final int[] values; + private final String[] otherEncodingNames; + + CharacterSetECI(int value) { + this(new int[] {value}); + } + + CharacterSetECI(int value, String... otherEncodingNames) { + this.values = new int[] {value}; + this.otherEncodingNames = otherEncodingNames; + } + + CharacterSetECI(int[] values, String... otherEncodingNames) { + this.values = values; + this.otherEncodingNames = otherEncodingNames; + } + + public int getValue() { + return values[0]; + } + + /** + * @param value character set ECI value + * @return {@code CharacterSetECI} representing ECI of given value, or null if it is legal but + * unsupported + * @throws FormatException if ECI value is invalid + */ + public static CharacterSetECI getCharacterSetECIByValue(int value) throws FormatException { + if (value < 0 || value >= 900) { + throw FormatException.getFormatInstance(); + } + return VALUE_TO_ECI.get(value); + } + + /** + * @param name character set ECI encoding name + * @return CharacterSetECI representing ECI for character encoding, or null if it is legal + * but unsupported + */ + public static CharacterSetECI getCharacterSetECIByName(String name) { + return NAME_TO_ECI.get(name); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/DecoderResult.java b/TMessagesProj/src/main/java/com/google/zxing/common/DecoderResult.java new file mode 100755 index 000000000..9a0d1b1f3 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/DecoderResult.java @@ -0,0 +1,152 @@ +/* + * 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.List; + +/** + *

Encapsulates 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 List byteSegments; + private final String ecLevel; + private Integer errorsCorrected; + private Integer erasures; + private Object other; + private final int structuredAppendParity; + private final int structuredAppendSequenceNumber; + + public DecoderResult(byte[] rawBytes, + String text, + List byteSegments, + String ecLevel) { + this(rawBytes, text, byteSegments, ecLevel, -1, -1); + } + + public DecoderResult(byte[] rawBytes, + String text, + List byteSegments, + String ecLevel, + int saSequence, + int saParity) { + this.rawBytes = rawBytes; + this.numBits = rawBytes == null ? 0 : 8 * rawBytes.length; + this.text = text; + this.byteSegments = byteSegments; + this.ecLevel = ecLevel; + this.structuredAppendParity = saParity; + this.structuredAppendSequenceNumber = saSequence; + } + + /** + * @return raw bytes representing the result, or {@code null} if not applicable + */ + public byte[] getRawBytes() { + return rawBytes; + } + + /** + * @return how many bits of {@link #getRawBytes()} are valid; typically 8 times its length + * @since 3.3.0 + */ + public int getNumBits() { + return numBits; + } + + /** + * @param numBits overrides the number of bits that are valid in {@link #getRawBytes()} + * @since 3.3.0 + */ + public void setNumBits(int numBits) { + this.numBits = numBits; + } + + /** + * @return text representation of the result + */ + public String getText() { + return text; + } + + /** + * @return list of byte segments in the result, or {@code null} if not applicable + */ + public List getByteSegments() { + return byteSegments; + } + + /** + * @return name of error correction level used, or {@code null} if not applicable + */ + public String getECLevel() { + return ecLevel; + } + + /** + * @return number of errors corrected, or {@code null} if not applicable + */ + public Integer getErrorsCorrected() { + return errorsCorrected; + } + + public void setErrorsCorrected(Integer errorsCorrected) { + this.errorsCorrected = errorsCorrected; + } + + /** + * @return number of erasures corrected, or {@code null} if not applicable + */ + public Integer getErasures() { + return erasures; + } + + public void setErasures(Integer erasures) { + this.erasures = erasures; + } + + /** + * @return arbitrary additional metadata + */ + public Object getOther() { + return other; + } + + public void setOther(Object other) { + this.other = other; + } + + public boolean hasStructuredAppend() { + return structuredAppendParity >= 0 && structuredAppendSequenceNumber >= 0; + } + + public int getStructuredAppendParity() { + return structuredAppendParity; + } + + public int getStructuredAppendSequenceNumber() { + return structuredAppendSequenceNumber; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/DefaultGridSampler.java b/TMessagesProj/src/main/java/com/google/zxing/common/DefaultGridSampler.java new file mode 100755 index 000000000..ce7a0c9f7 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/DefaultGridSampler.java @@ -0,0 +1,88 @@ +/* + * 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; + +/** + * @author Sean Owen + */ +public final class DefaultGridSampler extends GridSampler { + + @Override + public 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 { + + PerspectiveTransform transform = PerspectiveTransform.quadrilateralToQuadrilateral( + p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, + p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY); + + return sampleGrid(image, dimensionX, dimensionY, transform); + } + + @Override + public BitMatrix sampleGrid(BitMatrix image, + int dimensionX, + int dimensionY, + PerspectiveTransform transform) throws NotFoundException { + if (dimensionX <= 0 || dimensionY <= 0) { + throw NotFoundException.getNotFoundInstance(); + } + BitMatrix bits = new BitMatrix(dimensionX, dimensionY, 1); + float[] points = new float[2 * dimensionX]; + for (int y = 0; y < dimensionY; y++) { + int max = points.length; + float iValue = y + 0.5f; + for (int x = 0; x < max; x += 2) { + points[x] = (float) (x / 2) + 0.5f; + points[x + 1] = iValue; + } + transform.transformPoints(points); + // Quick check to see if points transformed to something inside the image; + // sufficient to check the endpoints + checkAndNudgePoints(image, points); + try { + for (int x = 0; x < max; x += 2) { + if (image.get((int) points[x], (int) points[x + 1])) { + // Black(-ish) pixel + bits.set(x / 2, y); + } + } + } catch (ArrayIndexOutOfBoundsException aioobe) { + // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting + // transform gets "twisted" such that it maps a straight line of points to a set of points + // whose endpoints are in bounds, but others are not. There is probably some mathematical + // way to detect this about the transformation that I don't know yet. + // This results in an ugly runtime exception despite our clever checks above -- can't have + // that. We could check each point's coordinates but that feels duplicative. We settle for + // catching and wrapping ArrayIndexOutOfBoundsException. + throw NotFoundException.getNotFoundInstance(); + } + } + return bits; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/DetectorResult.java b/TMessagesProj/src/main/java/com/google/zxing/common/DetectorResult.java new file mode 100755 index 000000000..0f3cf15e7 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/DetectorResult.java @@ -0,0 +1,46 @@ +/* + * 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.ResultPoint; + +/** + *

Encapsulates 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; + } + +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/GlobalHistogramBinarizer.java b/TMessagesProj/src/main/java/com/google/zxing/common/GlobalHistogramBinarizer.java new file mode 100755 index 000000000..f7f201525 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/GlobalHistogramBinarizer.java @@ -0,0 +1,203 @@ +/* + * 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, 1); + + // 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; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/GridSampler.java b/TMessagesProj/src/main/java/com/google/zxing/common/GridSampler.java new file mode 100755 index 000000000..43c3cf29b --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/GridSampler.java @@ -0,0 +1,174 @@ +/* + * 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; + } + } + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/HybridBinarizer.java b/TMessagesProj/src/main/java/com/google/zxing/common/HybridBinarizer.java new file mode 100755 index 000000000..f97992ab9 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/HybridBinarizer.java @@ -0,0 +1,237 @@ +/* + * 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, 1); + 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, subHeight - 3); + for (int x = 0; x < subWidth; x++) { + int xoffset = x << BLOCK_SIZE_POWER; + if (xoffset > maxXOffset) { + xoffset = maxXOffset; + } + int left = cap(x, 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 max) { + return value < 2 ? 2 : Math.min(value, max); + } + + /** + * 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; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/PerspectiveTransform.java b/TMessagesProj/src/main/java/com/google/zxing/common/PerspectiveTransform.java new file mode 100755 index 000000000..06dba94b9 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/PerspectiveTransform.java @@ -0,0 +1,156 @@ +/* + * 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); + + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/StringUtils.java b/TMessagesProj/src/main/java/com/google/zxing/common/StringUtils.java new file mode 100755 index 000000000..2eb3a3145 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/StringUtils.java @@ -0,0 +1,197 @@ +/* + * 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, Map hints) { + if (hints != null && hints.containsKey(DecodeHintType.CHARACTER_SET)) { + return hints.get(DecodeHintType.CHARACTER_SET).toString(); + } + // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS, + // which should be by far the most common encodings. + int length = bytes.length; + boolean canBeISO88591 = true; + boolean canBeShiftJIS = true; + boolean canBeUTF8 = true; + int utf8BytesLeft = 0; + int utf2BytesChars = 0; + int utf3BytesChars = 0; + int utf4BytesChars = 0; + int sjisBytesLeft = 0; + int sjisKatakanaChars = 0; + int sjisCurKatakanaWordLength = 0; + int sjisCurDoubleBytesWordLength = 0; + int sjisMaxKatakanaWordLength = 0; + int sjisMaxDoubleBytesWordLength = 0; + int isoHighOther = 0; + + boolean utf8bom = bytes.length > 3 && + bytes[0] == (byte) 0xEF && + bytes[1] == (byte) 0xBB && + bytes[2] == (byte) 0xBF; + + for (int i = 0; + i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8); + i++) { + + int value = bytes[i] & 0xFF; + + // UTF-8 stuff + if (canBeUTF8) { + if (utf8BytesLeft > 0) { + if ((value & 0x80) == 0) { + canBeUTF8 = false; + } else { + utf8BytesLeft--; + } + } else if ((value & 0x80) != 0) { + if ((value & 0x40) == 0) { + canBeUTF8 = false; + } else { + utf8BytesLeft++; + if ((value & 0x20) == 0) { + utf2BytesChars++; + } else { + utf8BytesLeft++; + if ((value & 0x10) == 0) { + utf3BytesChars++; + } else { + utf8BytesLeft++; + if ((value & 0x08) == 0) { + utf4BytesChars++; + } else { + canBeUTF8 = false; + } + } + } + } + } + } + + // ISO-8859-1 stuff + if (canBeISO88591) { + if (value > 0x7F && value < 0xA0) { + canBeISO88591 = false; + } else if (value > 0x9F && (value < 0xC0 || value == 0xD7 || value == 0xF7)) { + isoHighOther++; + } + } + + // Shift_JIS stuff + if (canBeShiftJIS) { + if (sjisBytesLeft > 0) { + if (value < 0x40 || value == 0x7F || value > 0xFC) { + canBeShiftJIS = false; + } else { + sjisBytesLeft--; + } + } else if (value == 0x80 || value == 0xA0 || value > 0xEF) { + canBeShiftJIS = false; + } else if (value > 0xA0 && value < 0xE0) { + sjisKatakanaChars++; + sjisCurDoubleBytesWordLength = 0; + sjisCurKatakanaWordLength++; + if (sjisCurKatakanaWordLength > sjisMaxKatakanaWordLength) { + sjisMaxKatakanaWordLength = sjisCurKatakanaWordLength; + } + } else if (value > 0x7F) { + sjisBytesLeft++; + //sjisDoubleBytesChars++; + sjisCurKatakanaWordLength = 0; + sjisCurDoubleBytesWordLength++; + if (sjisCurDoubleBytesWordLength > sjisMaxDoubleBytesWordLength) { + sjisMaxDoubleBytesWordLength = sjisCurDoubleBytesWordLength; + } + } else { + //sjisLowChars++; + sjisCurKatakanaWordLength = 0; + sjisCurDoubleBytesWordLength = 0; + } + } + } + + if (canBeUTF8 && utf8BytesLeft > 0) { + canBeUTF8 = false; + } + if (canBeShiftJIS && sjisBytesLeft > 0) { + canBeShiftJIS = false; + } + + // Easy -- if there is BOM or at least 1 valid not-single byte character (and no evidence it can't be UTF-8), done + if (canBeUTF8 && (utf8bom || utf2BytesChars + utf3BytesChars + utf4BytesChars > 0)) { + return UTF8; + } + // Easy -- if assuming Shift_JIS or >= 3 valid consecutive not-ascii characters (and no evidence it can't be), done + if (canBeShiftJIS && (ASSUME_SHIFT_JIS || sjisMaxKatakanaWordLength >= 3 || sjisMaxDoubleBytesWordLength >= 3)) { + return SHIFT_JIS; + } + // Distinguishing Shift_JIS and ISO-8859-1 can be a little tough for short words. The crude heuristic is: + // - If we saw + // - only two consecutive katakana chars in the whole text, or + // - at least 10% of bytes that could be "upper" not-alphanumeric Latin1, + // - then we conclude Shift_JIS, else ISO-8859-1 + if (canBeISO88591 && canBeShiftJIS) { + return (sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || isoHighOther * 10 >= length + ? SHIFT_JIS : ISO88591; + } + + // Otherwise, try in order ISO-8859-1, Shift JIS, UTF-8 and fall back to default platform encoding + if (canBeISO88591) { + return ISO88591; + } + if (canBeShiftJIS) { + return SHIFT_JIS; + } + if (canBeUTF8) { + return UTF8; + } + // Otherwise, we take a wild guess with platform encoding + return PLATFORM_DEFAULT_ENCODING; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/detector/MathUtils.java b/TMessagesProj/src/main/java/com/google/zxing/common/detector/MathUtils.java new file mode 100755 index 000000000..bd5931b31 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/detector/MathUtils.java @@ -0,0 +1,78 @@ +/* + * 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.common.detector; + +/** + * General math-related and numeric utility functions. + */ +public final class MathUtils { + + private MathUtils() { + } + + /** + * Ends up being a bit faster than {@link Math#round(float)}. This merely rounds its + * argument to the nearest int, where x.5 rounds up to x+1. Semantics of this shortcut + * differ slightly from {@link Math#round(float)} in that half rounds down for negative + * values. -2.5 rounds to -3, not -2. For purposes here it makes no difference. + * + * @param d real value to round + * @return nearest {@code int} + */ + public static int round(float d) { + return (int) (d + (d < 0.0f ? -0.5f : 0.5f)); + } + + /** + * @param aX point A x coordinate + * @param aY point A y coordinate + * @param bX point B x coordinate + * @param bY point B y coordinate + * @return Euclidean distance between points A and B + */ + public static float distance(float aX, float aY, float bX, float bY) { + double xDiff = aX - bX; + double yDiff = aY - bY; + return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff); + } + + /** + * @param aX point A x coordinate + * @param aY point A y coordinate + * @param bX point B x coordinate + * @param bY point B y coordinate + * @return Euclidean distance between points A and B + */ + public static float distance(int aX, int aY, int bX, int bY) { + double xDiff = aX - bX; + double yDiff = aY - bY; + return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff); + } + + /** + * @param array values to sum + * @return sum of values in array + */ + public static int sum(int[] array) { + int count = 0; + for (int a : array) { + count += a; + } + return count; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/detector/MonochromeRectangleDetector.java b/TMessagesProj/src/main/java/com/google/zxing/common/detector/MonochromeRectangleDetector.java new file mode 100755 index 000000000..e87fa5531 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/detector/MonochromeRectangleDetector.java @@ -0,0 +1,217 @@ +/* + * 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.detector; + +import com.google.zxing.NotFoundException; +import com.google.zxing.ResultPoint; +import com.google.zxing.common.BitMatrix; + +/** + *

A 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; + } + +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/detector/WhiteRectangleDetector.java b/TMessagesProj/src/main/java/com/google/zxing/common/detector/WhiteRectangleDetector.java new file mode 100755 index 000000000..8d77b0de5 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/detector/WhiteRectangleDetector.java @@ -0,0 +1,325 @@ +/* + * 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; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/GenericGF.java b/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/GenericGF.java new file mode 100755 index 000000000..2b32566c0 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/GenericGF.java @@ -0,0 +1,166 @@ +/* + * 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 + ')'; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/GenericGFPoly.java b/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/GenericGFPoly.java new file mode 100755 index 000000000..03f67430d --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/GenericGFPoly.java @@ -0,0 +1,271 @@ +/* + * 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(); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java b/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java new file mode 100755 index 000000000..e406e2f00 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java @@ -0,0 +1,190 @@ +/* + * 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; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java b/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java new file mode 100755 index 000000000..2e2b2f0a6 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java @@ -0,0 +1,74 @@ +/* + * 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 List cachedGenerators; + + public ReedSolomonEncoder(GenericGF field) { + this.field = field; + this.cachedGenerators = new ArrayList<>(); + cachedGenerators.add(new GenericGFPoly(field, new int[]{1})); + } + + private GenericGFPoly buildGenerator(int degree) { + if (degree >= cachedGenerators.size()) { + GenericGFPoly lastGenerator = cachedGenerators.get(cachedGenerators.size() - 1); + for (int d = cachedGenerators.size(); d <= degree; d++) { + GenericGFPoly nextGenerator = lastGenerator.multiply( + new GenericGFPoly(field, new int[] { 1, field.exp(d - 1 + field.getGeneratorBase()) })); + cachedGenerators.add(nextGenerator); + lastGenerator = nextGenerator; + } + } + return cachedGenerators.get(degree); + } + + public void encode(int[] toEncode, int ecBytes) { + if (ecBytes == 0) { + throw new IllegalArgumentException("No error correction bytes"); + } + int dataBytes = toEncode.length - ecBytes; + if (dataBytes <= 0) { + throw new IllegalArgumentException("No data bytes provided"); + } + GenericGFPoly generator = buildGenerator(ecBytes); + int[] infoCoefficients = new int[dataBytes]; + System.arraycopy(toEncode, 0, infoCoefficients, 0, dataBytes); + GenericGFPoly info = new GenericGFPoly(field, infoCoefficients); + info = info.multiplyByMonomial(ecBytes, 1); + GenericGFPoly remainder = info.divide(generator)[1]; + int[] coefficients = remainder.getCoefficients(); + int numZeroCoefficients = ecBytes - coefficients.length; + for (int i = 0; i < numZeroCoefficients; i++) { + toEncode[dataBytes + i] = 0; + } + System.arraycopy(coefficients, 0, toEncode, dataBytes + numZeroCoefficients, coefficients.length); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonException.java b/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonException.java new file mode 100755 index 000000000..d5b45a612 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonException.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + *

Thrown 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); + } + +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/com/google/zxing/multi/ByQuadrantReader.java b/TMessagesProj/src/main/java/com/google/zxing/multi/ByQuadrantReader.java new file mode 100755 index 000000000..6674c8f35 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/multi/ByQuadrantReader.java @@ -0,0 +1,117 @@ +/* + * 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.multi; + +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.ResultPoint; + +import java.util.Map; + +/** + * This class attempts to decode a barcode from an image, not by scanning the whole image, + * but by scanning subsets of the image. This is important when there may be multiple barcodes in + * an image, and detecting a barcode may find parts of multiple barcode and fail to decode + * (e.g. QR Codes). Instead this scans the four quadrants of the image -- and also the center + * 'quadrant' to cover the case where a barcode is found in the center. + * + * @see GenericMultipleBarcodeReader + */ +public final class ByQuadrantReader implements Reader { + + private final Reader delegate; + + public ByQuadrantReader(Reader delegate) { + this.delegate = delegate; + } + + @Override + public Result decode(BinaryBitmap image) + throws NotFoundException, ChecksumException, FormatException { + return decode(image, null); + } + + @Override + public Result decode(BinaryBitmap image, Map hints) + throws NotFoundException, ChecksumException, FormatException { + + int width = image.getWidth(); + int height = image.getHeight(); + int halfWidth = width / 2; + int halfHeight = height / 2; + + try { + // No need to call makeAbsolute as results will be relative to original top left here + return delegate.decode(image.crop(0, 0, halfWidth, halfHeight), hints); + } catch (NotFoundException re) { + // continue + } + + try { + Result result = delegate.decode(image.crop(halfWidth, 0, halfWidth, halfHeight), hints); + makeAbsolute(result.getResultPoints(), halfWidth, 0); + return result; + } catch (NotFoundException re) { + // continue + } + + try { + Result result = delegate.decode(image.crop(0, halfHeight, halfWidth, halfHeight), hints); + makeAbsolute(result.getResultPoints(), 0, halfHeight); + return result; + } catch (NotFoundException re) { + // continue + } + + try { + Result result = delegate.decode(image.crop(halfWidth, halfHeight, halfWidth, halfHeight), hints); + makeAbsolute(result.getResultPoints(), halfWidth, halfHeight); + return result; + } catch (NotFoundException re) { + // continue + } + + int quarterWidth = halfWidth / 2; + int quarterHeight = halfHeight / 2; + BinaryBitmap center = image.crop(quarterWidth, quarterHeight, halfWidth, halfHeight); + Result result = delegate.decode(center, hints); + makeAbsolute(result.getResultPoints(), quarterWidth, quarterHeight); + return result; + } + + @Override + public void reset() { + delegate.reset(); + } + + private static void makeAbsolute(ResultPoint[] points, int leftOffset, int topOffset) { + if (points != null) { + for (int i = 0; i < points.length; i++) { + ResultPoint relative = points[i]; + if (relative != null) { + points[i] = new ResultPoint(relative.getX() + leftOffset, relative.getY() + topOffset); + } + } + } + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/multi/GenericMultipleBarcodeReader.java b/TMessagesProj/src/main/java/com/google/zxing/multi/GenericMultipleBarcodeReader.java new file mode 100755 index 000000000..5e53419b5 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/multi/GenericMultipleBarcodeReader.java @@ -0,0 +1,182 @@ +/* + * 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.multi; + +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.NotFoundException; +import com.google.zxing.Reader; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.ResultPoint; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + *

Attempts 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, Map hints) + throws NotFoundException { + List results = new ArrayList<>(); + doDecodeMultiple(image, hints, results, 0, 0, 0); + if (results.isEmpty()) { + throw NotFoundException.getNotFoundInstance(); + } + return results.toArray(EMPTY_RESULT_ARRAY); + } + + private void doDecodeMultiple(BinaryBitmap image, + Map hints, + List results, + int xOffset, + int yOffset, + int currentDepth) { + if (currentDepth > MAX_DEPTH) { + return; + } + + Result result; + try { + result = delegate.decode(image, hints); + } catch (ReaderException ignored) { + return; + } + boolean alreadyFound = false; + for (Result existingResult : results) { + if (existingResult.getText().equals(result.getText())) { + alreadyFound = true; + break; + } + } + if (!alreadyFound) { + results.add(translateResultPoints(result, xOffset, yOffset)); + } + ResultPoint[] resultPoints = result.getResultPoints(); + if (resultPoints == null || resultPoints.length == 0) { + return; + } + int width = image.getWidth(); + int height = image.getHeight(); + float minX = width; + float minY = height; + float maxX = 0.0f; + float maxY = 0.0f; + for (ResultPoint point : resultPoints) { + if (point == null) { + continue; + } + float x = point.getX(); + float y = point.getY(); + if (x < minX) { + minX = x; + } + if (y < minY) { + minY = y; + } + if (x > maxX) { + maxX = x; + } + if (y > maxY) { + maxY = y; + } + } + + // Decode left of barcode + if (minX > MIN_DIMENSION_TO_RECUR) { + doDecodeMultiple(image.crop(0, 0, (int) minX, height), + hints, results, + xOffset, yOffset, + currentDepth + 1); + } + // Decode above barcode + if (minY > MIN_DIMENSION_TO_RECUR) { + doDecodeMultiple(image.crop(0, 0, width, (int) minY), + hints, results, + xOffset, yOffset, + currentDepth + 1); + } + // Decode right of barcode + if (maxX < width - MIN_DIMENSION_TO_RECUR) { + doDecodeMultiple(image.crop((int) maxX, 0, width - (int) maxX, height), + hints, results, + xOffset + (int) maxX, yOffset, + currentDepth + 1); + } + // Decode below barcode + if (maxY < height - MIN_DIMENSION_TO_RECUR) { + doDecodeMultiple(image.crop(0, (int) maxY, width, height - (int) maxY), + hints, results, + xOffset, yOffset + (int) maxY, + currentDepth + 1); + } + } + + private static Result translateResultPoints(Result result, int xOffset, int yOffset) { + ResultPoint[] oldResultPoints = result.getResultPoints(); + if (oldResultPoints == null) { + return result; + } + ResultPoint[] newResultPoints = new ResultPoint[oldResultPoints.length]; + for (int i = 0; i < oldResultPoints.length; i++) { + ResultPoint oldPoint = oldResultPoints[i]; + if (oldPoint != null) { + newResultPoints[i] = new ResultPoint(oldPoint.getX() + xOffset, oldPoint.getY() + yOffset); + } + } + Result newResult = new Result(result.getText(), + result.getRawBytes(), + result.getNumBits(), + newResultPoints, + result.getBarcodeFormat(), + result.getTimestamp()); + newResult.putAllMetadata(result.getResultMetadata()); + return newResult; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/multi/MultipleBarcodeReader.java b/TMessagesProj/src/main/java/com/google/zxing/multi/MultipleBarcodeReader.java new file mode 100755 index 000000000..a35872799 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/multi/MultipleBarcodeReader.java @@ -0,0 +1,39 @@ +/* + * 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.multi; + +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.NotFoundException; +import com.google.zxing.Result; + +import java.util.Map; + +/** + * Implementation of this interface attempt to read several barcodes from one image. + * + * @see com.google.zxing.Reader + * @author Sean Owen + */ +public interface MultipleBarcodeReader { + + Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException; + + Result[] decodeMultiple(BinaryBitmap image, + Map hints) throws NotFoundException; + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/QRCodeMultiReader.java b/TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/QRCodeMultiReader.java new file mode 100755 index 000000000..c3663498b --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/QRCodeMultiReader.java @@ -0,0 +1,149 @@ +/* + * 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.multi.qrcode; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.NotFoundException; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.ResultMetadataType; +import com.google.zxing.ResultPoint; +import com.google.zxing.common.DecoderResult; +import com.google.zxing.common.DetectorResult; +import com.google.zxing.multi.MultipleBarcodeReader; +import com.google.zxing.multi.qrcode.detector.MultiDetector; +import com.google.zxing.qrcode.QRCodeReader; +import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData; + +import java.io.ByteArrayOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Collections; +import java.util.Comparator; + +/** + * This implementation can detect and decode multiple QR Codes in an image. + * + * @author Sean Owen + * @author Hannes Erven + */ +public final class QRCodeMultiReader extends QRCodeReader implements MultipleBarcodeReader { + + private static final Result[] EMPTY_RESULT_ARRAY = new Result[0]; + private static final ResultPoint[] NO_POINTS = new ResultPoint[0]; + + @Override + public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException { + return decodeMultiple(image, null); + } + + @Override + public Result[] decodeMultiple(BinaryBitmap image, Map hints) throws NotFoundException { + List results = new ArrayList<>(); + DetectorResult[] detectorResults = new MultiDetector(image.getBlackMatrix()).detectMulti(hints); + for (DetectorResult detectorResult : detectorResults) { + try { + DecoderResult decoderResult = getDecoder().decode(detectorResult.getBits(), hints); + ResultPoint[] points = detectorResult.getPoints(); + // If the code was mirrored: swap the bottom-left and the top-right points. + if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) { + ((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points); + } + Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, + BarcodeFormat.QR_CODE); + List byteSegments = decoderResult.getByteSegments(); + if (byteSegments != null) { + result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments); + } + String ecLevel = decoderResult.getECLevel(); + if (ecLevel != null) { + result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel); + } + if (decoderResult.hasStructuredAppend()) { + result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, + decoderResult.getStructuredAppendSequenceNumber()); + result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY, + decoderResult.getStructuredAppendParity()); + } + results.add(result); + } catch (ReaderException re) { + // ignore and continue + } + } + if (results.isEmpty()) { + return EMPTY_RESULT_ARRAY; + } else { + results = processStructuredAppend(results); + return results.toArray(EMPTY_RESULT_ARRAY); + } + } + + static List processStructuredAppend(List results) { + List newResults = new ArrayList<>(); + List saResults = new ArrayList<>(); + for (Result result : results) { + if (result.getResultMetadata().containsKey(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE)) { + saResults.add(result); + } else { + newResults.add(result); + } + } + if (saResults.isEmpty()) { + return results; + } + + // sort and concatenate the SA list items + Collections.sort(saResults, new SAComparator()); + StringBuilder newText = new StringBuilder(); + ByteArrayOutputStream newRawBytes = new ByteArrayOutputStream(); + ByteArrayOutputStream newByteSegment = new ByteArrayOutputStream(); + for (Result saResult : saResults) { + newText.append(saResult.getText()); + byte[] saBytes = saResult.getRawBytes(); + newRawBytes.write(saBytes, 0, saBytes.length); + @SuppressWarnings("unchecked") + Iterable byteSegments = + (Iterable) saResult.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS); + if (byteSegments != null) { + for (byte[] segment : byteSegments) { + newByteSegment.write(segment, 0, segment.length); + } + } + } + + Result newResult = new Result(newText.toString(), newRawBytes.toByteArray(), NO_POINTS, BarcodeFormat.QR_CODE); + if (newByteSegment.size() > 0) { + newResult.putMetadata(ResultMetadataType.BYTE_SEGMENTS, Collections.singletonList(newByteSegment.toByteArray())); + } + newResults.add(newResult); + return newResults; + } + + private static final class SAComparator implements Comparator, Serializable { + @Override + public int compare(Result a, Result b) { + int aNumber = (int) a.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE); + int bNumber = (int) b.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE); + return Integer.compare(aNumber, bNumber); + } + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/detector/MultiDetector.java b/TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/detector/MultiDetector.java new file mode 100755 index 000000000..512b452c8 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/detector/MultiDetector.java @@ -0,0 +1,73 @@ +/* + * 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.multi.qrcode.detector; + +import com.google.zxing.DecodeHintType; +import com.google.zxing.NotFoundException; +import com.google.zxing.ReaderException; +import com.google.zxing.ResultPointCallback; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.DetectorResult; +import com.google.zxing.qrcode.detector.Detector; +import com.google.zxing.qrcode.detector.FinderPatternInfo; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + *

Encapsulates 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(Map hints) throws NotFoundException { + BitMatrix image = getImage(); + ResultPointCallback resultPointCallback = + hints == null ? null : (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); + MultiFinderPatternFinder finder = new MultiFinderPatternFinder(image, resultPointCallback); + FinderPatternInfo[] infos = finder.findMulti(hints); + + if (infos.length == 0) { + throw NotFoundException.getNotFoundInstance(); + } + + List result = new ArrayList<>(); + for (FinderPatternInfo info : infos) { + try { + result.add(processFinderPatternInfo(info)); + } catch (ReaderException e) { + // ignore + } + } + if (result.isEmpty()) { + return EMPTY_DETECTOR_RESULTS; + } else { + return result.toArray(EMPTY_DETECTOR_RESULTS); + } + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java b/TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java new file mode 100755 index 000000000..c5ecd6adf --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java @@ -0,0 +1,285 @@ +/* + * 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.multi.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 com.google.zxing.qrcode.detector.FinderPattern; +import com.google.zxing.qrcode.detector.FinderPatternFinder; +import com.google.zxing.qrcode.detector.FinderPatternInfo; + +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. + * + *

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_ARRAY = new FinderPattern[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 Comparator, Serializable { + @Override + public int compare(FinderPattern center1, FinderPattern center2) { + float value = center2.getEstimatedModuleSize() - center1.getEstimatedModuleSize(); + return value < 0.0 ? -1 : value > 0.0 ? 1 : 0; + } + } + + 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 2 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 { + List possibleCenters = getPossibleCenters(); + int size = possibleCenters.size(); + + if (size < 3) { + // Couldn't find enough finder patterns + throw NotFoundException.getNotFoundInstance(); + } + + /* + * Begin HE modifications to safely detect multiple codes of equal size + */ + if (size == 3) { + return new FinderPattern[][] { possibleCenters.toArray(EMPTY_FP_ARRAY) }; + } + + // Sort by estimated module size to speed up the upcoming checks + Collections.sort(possibleCenters, new ModuleSizeComparator()); + + /* + * Now lets start: build a list of tuples of three finder locations that + * - feature similar module sizes + * - are placed in a distance so the estimated module count is within the QR specification + * - have similar distance between upper left/right and left top/bottom finder patterns + * - form a triangle with 90° angle (checked by comparing top right/bottom left distance + * with pythagoras) + * + * Note: we allow each point to be used for more than one code region: this might seem + * counterintuitive at first, but the performance penalty is not that big. At this point, + * we cannot make a good quality decision whether the three finders actually represent + * a QR code, or are just by chance laid out so it looks like there might be a QR code there. + * So, if the layout seems right, lets have the decoder try to decode. + */ + + List results = new ArrayList<>(); // holder for the results + + for (int i1 = 0; i1 < (size - 2); i1++) { + FinderPattern p1 = possibleCenters.get(i1); + if (p1 == null) { + continue; + } + + for (int i2 = i1 + 1; i2 < (size - 1); i2++) { + FinderPattern p2 = possibleCenters.get(i2); + if (p2 == null) { + continue; + } + + // Compare the expected module sizes; if they are really off, skip + float vModSize12 = (p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()) / + Math.min(p1.getEstimatedModuleSize(), p2.getEstimatedModuleSize()); + float vModSize12A = Math.abs(p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()); + if (vModSize12A > DIFF_MODSIZE_CUTOFF && vModSize12 >= DIFF_MODSIZE_CUTOFF_PERCENT) { + // break, since elements are ordered by the module size deviation there cannot be + // any more interesting elements for the given p1. + break; + } + + for (int i3 = i2 + 1; i3 < size; i3++) { + FinderPattern p3 = possibleCenters.get(i3); + if (p3 == null) { + continue; + } + + // Compare the expected module sizes; if they are really off, skip + float vModSize23 = (p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()) / + Math.min(p2.getEstimatedModuleSize(), p3.getEstimatedModuleSize()); + float vModSize23A = Math.abs(p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()); + if (vModSize23A > DIFF_MODSIZE_CUTOFF && vModSize23 >= DIFF_MODSIZE_CUTOFF_PERCENT) { + // break, since elements are ordered by the module size deviation there cannot be + // any more interesting elements for the given p1. + break; + } + + FinderPattern[] test = {p1, p2, p3}; + ResultPoint.orderBestPatterns(test); + + // Calculate the distances: a = topleft-bottomleft, b=topleft-topright, c = diagonal + FinderPatternInfo info = new FinderPatternInfo(test); + float dA = ResultPoint.distance(info.getTopLeft(), info.getBottomLeft()); + float dC = ResultPoint.distance(info.getTopRight(), info.getBottomLeft()); + float dB = ResultPoint.distance(info.getTopLeft(), info.getTopRight()); + + // Check the sizes + float estimatedModuleCount = (dA + dB) / (p1.getEstimatedModuleSize() * 2.0f); + if (estimatedModuleCount > MAX_MODULE_COUNT_PER_EDGE || + estimatedModuleCount < MIN_MODULE_COUNT_PER_EDGE) { + continue; + } + + // Calculate the difference of the edge lengths in percent + float vABBC = Math.abs((dA - dB) / Math.min(dA, dB)); + if (vABBC >= 0.1f) { + continue; + } + + // Calculate the diagonal length by assuming a 90° angle at topleft + float dCpy = (float) Math.sqrt((double) dA * dA + (double) dB * dB); + // Compare to the real distance in % + float vPyC = Math.abs((dC - dCpy) / Math.min(dC, dCpy)); + + if (vPyC >= 0.1f) { + continue; + } + + // All tests passed! + results.add(test); + } + } + } + + if (!results.isEmpty()) { + return results.toArray(EMPTY_FP_2D_ARRAY); + } + + // Nothing found! + throw NotFoundException.getNotFoundInstance(); + } + + public FinderPatternInfo[] findMulti(Map hints) throws NotFoundException { + boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); + BitMatrix image = getImage(); + int maxI = image.getHeight(); + int maxJ = image.getWidth(); + // We are looking for black/white/black/white/black modules in + // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far + + // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the + // image, and then account for the center being 3 modules in size. This gives the smallest + // number of pixels the center could be, so skip this often. When trying harder, look for all + // QR versions regardless of how dense they are. + int iSkip = (3 * maxI) / (4 * MAX_MODULES); + if (iSkip < MIN_SKIP || tryHarder) { + iSkip = MIN_SKIP; + } + + int[] stateCount = new int[5]; + for (int i = iSkip - 1; i < maxI; i += iSkip) { + // Get a row of black/white values + clearCounts(stateCount); + int currentState = 0; + for (int j = 0; j < maxJ; j++) { + if (image.get(j, i)) { + // Black pixel + if ((currentState & 1) == 1) { // Counting white pixels + currentState++; + } + stateCount[currentState]++; + } else { // White pixel + if ((currentState & 1) == 0) { // Counting black pixels + if (currentState == 4) { // A winner? + if (foundPatternCross(stateCount) && handlePossibleCenter(stateCount, i, j)) { // Yes + // Clear state to start looking again + currentState = 0; + clearCounts(stateCount); + } else { // No, shift counts back by two + shiftCounts2(stateCount); + currentState = 3; + } + } else { + stateCount[++currentState]++; + } + } else { // Counting white pixels + stateCount[currentState]++; + } + } + } // for j=... + + if (foundPatternCross(stateCount)) { + handlePossibleCenter(stateCount, i, maxJ); + } + } // for i=iSkip-1 ... + FinderPattern[][] patternInfo = selectMultipleBestPatterns(); + List result = new ArrayList<>(); + for (FinderPattern[] pattern : patternInfo) { + ResultPoint.orderBestPatterns(pattern); + result.add(new FinderPatternInfo(pattern)); + } + + if (result.isEmpty()) { + return EMPTY_RESULT_ARRAY; + } else { + return result.toArray(EMPTY_RESULT_ARRAY); + } + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/QRCodeReader.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/QRCodeReader.java new file mode 100755 index 000000000..a47a8d00e --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/QRCodeReader.java @@ -0,0 +1,220 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.ChecksumException; +import com.google.zxing.DecodeHintType; +import com.google.zxing.FormatException; +import com.google.zxing.NotFoundException; +import com.google.zxing.Reader; +import com.google.zxing.Result; +import com.google.zxing.ResultMetadataType; +import com.google.zxing.ResultPoint; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.DecoderResult; +import com.google.zxing.common.DetectorResult; +import com.google.zxing.qrcode.decoder.Decoder; +import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData; +import com.google.zxing.qrcode.detector.Detector; + +import java.util.List; +import java.util.Map; + +/** + * This implementation can detect and decode QR Codes in an image. + * + * @author Sean Owen + */ +public class QRCodeReader implements Reader { + + private static final ResultPoint[] NO_POINTS = new ResultPoint[0]; + + private final Decoder decoder = new Decoder(); + + protected final Decoder getDecoder() { + return decoder; + } + + /** + * Locates and decodes a QR code in an image. + * + * @return a String representing the content encoded by the QR code + * @throws NotFoundException if a QR code cannot be found + * @throws FormatException if a QR code cannot be decoded + * @throws ChecksumException if error correction fails + */ + @Override + public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException { + return decode(image, null); + } + + @Override + public final Result decode(BinaryBitmap image, Map hints) + throws NotFoundException, ChecksumException, FormatException { + DecoderResult decoderResult; + ResultPoint[] points; + if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) { + BitMatrix bits = extractPureBits(image.getBlackMatrix()); + decoderResult = decoder.decode(bits, hints); + points = NO_POINTS; + } else { + DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints); + decoderResult = decoder.decode(detectorResult.getBits(), hints); + points = detectorResult.getPoints(); + } + + // If the code was mirrored: swap the bottom-left and the top-right points. + if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) { + ((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points); + } + + Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE); + List byteSegments = decoderResult.getByteSegments(); + if (byteSegments != null) { + result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments); + } + String ecLevel = decoderResult.getECLevel(); + if (ecLevel != null) { + result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel); + } + if (decoderResult.hasStructuredAppend()) { + result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, + decoderResult.getStructuredAppendSequenceNumber()); + result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY, + decoderResult.getStructuredAppendParity()); + } + return result; + } + + @Override + public void reset() { + // do nothing + } + + /** + * This method detects a code in a "pure" image -- that is, pure monochrome image + * which contains only an unrotated, unskewed, image of a code, with some white border + * around it. This is a specialized method that works exceptionally fast in this special + * case. + */ + private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException { + + int[] leftTopBlack = image.getTopLeftOnBit(); + int[] rightBottomBlack = image.getBottomRightOnBit(); + if (leftTopBlack == null || rightBottomBlack == null) { + throw NotFoundException.getNotFoundInstance(); + } + + float moduleSize = moduleSize(leftTopBlack, image); + + int top = leftTopBlack[1]; + int bottom = rightBottomBlack[1]; + int left = leftTopBlack[0]; + int right = rightBottomBlack[0]; + + // Sanity check! + if (left >= right || top >= bottom) { + throw NotFoundException.getNotFoundInstance(); + } + + if (bottom - top != right - left) { + // Special case, where bottom-right module wasn't black so we found something else in the last row + // Assume it's a square, so use height as the width + right = left + (bottom - top); + if (right >= image.getWidth()) { + // Abort if that would not make sense -- off image + throw NotFoundException.getNotFoundInstance(); + } + } + + int matrixWidth = Math.round((right - left + 1) / moduleSize); + int matrixHeight = Math.round((bottom - top + 1) / moduleSize); + if (matrixWidth <= 0 || matrixHeight <= 0) { + throw NotFoundException.getNotFoundInstance(); + } + if (matrixHeight != matrixWidth) { + // Only possibly decode square regions + throw NotFoundException.getNotFoundInstance(); + } + + // Push in the "border" by half the module width so that we start + // sampling in the middle of the module. Just in case the image is a + // little off, this will help recover. + int nudge = (int) (moduleSize / 2.0f); + top += nudge; + left += nudge; + + // But careful that this does not sample off the edge + // "right" is the farthest-right valid pixel location -- right+1 is not necessarily + // This is positive by how much the inner x loop below would be too large + int nudgedTooFarRight = left + (int) ((matrixWidth - 1) * moduleSize) - right; + if (nudgedTooFarRight > 0) { + if (nudgedTooFarRight > nudge) { + // Neither way fits; abort + throw NotFoundException.getNotFoundInstance(); + } + left -= nudgedTooFarRight; + } + // See logic above + int nudgedTooFarDown = top + (int) ((matrixHeight - 1) * moduleSize) - bottom; + if (nudgedTooFarDown > 0) { + if (nudgedTooFarDown > nudge) { + // Neither way fits; abort + throw NotFoundException.getNotFoundInstance(); + } + top -= nudgedTooFarDown; + } + + // Now just read off the bits + BitMatrix bits = new BitMatrix(matrixWidth, matrixHeight, 1); + for (int y = 0; y < matrixHeight; y++) { + int iOffset = top + (int) (y * moduleSize); + for (int x = 0; x < matrixWidth; x++) { + if (image.get(left + (int) (x * moduleSize), iOffset)) { + bits.set(x, y); + } + } + } + return bits; + } + + private static float moduleSize(int[] leftTopBlack, BitMatrix image) throws NotFoundException { + int height = image.getHeight(); + int width = image.getWidth(); + int x = leftTopBlack[0]; + int y = leftTopBlack[1]; + boolean inBlack = true; + int transitions = 0; + while (x < width && y < height) { + if (inBlack != image.get(x, y)) { + if (++transitions == 5) { + break; + } + inBlack = !inBlack; + } + x++; + y++; + } + if (x == width || y == height) { + throw NotFoundException.getNotFoundInstance(); + } + return (x - leftTopBlack[0]) / 7.0f; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/QRCodeWriter.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/QRCodeWriter.java new file mode 100755 index 000000000..91bbd1f3b --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/QRCodeWriter.java @@ -0,0 +1,246 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.qrcode.encoder.ByteMatrix; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.google.zxing.qrcode.encoder.Encoder; +import com.google.zxing.qrcode.encoder.QRCode; + +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.R; +import org.telegram.messenger.SvgHelper; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.RLottieDrawable; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; + +/** + * This object renders a QR Code as a BitMatrix 2D array of greyscale values. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class QRCodeWriter { + + private static final int QUIET_ZONE_SIZE = 4; + private ByteMatrix input; + private float[] radii = new float[8]; + private int imageBloks; + private int imageBlockX; + private int sideQuadSize; + + public Bitmap encode(String contents, BarcodeFormat format, int width, int height, Map hints, Bitmap bitmap, Context context) throws WriterException { + return encode(contents, format, width, height, hints, bitmap, context, null); + } + + public Bitmap encode(String contents, BarcodeFormat format, int width, int height, Map hints, Bitmap bitmap, Context context, Function iconF) throws WriterException { + + if (contents.isEmpty()) { + throw new IllegalArgumentException("Found empty contents"); + } + + if (width < 0 || height < 0) { + throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' + height); + } + + ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L; + int quietZone = QUIET_ZONE_SIZE; + if (hints != null) { + if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) { + errorCorrectionLevel = ErrorCorrectionLevel.valueOf(hints.get(EncodeHintType.ERROR_CORRECTION).toString()); + } + if (hints.containsKey(EncodeHintType.MARGIN)) { + quietZone = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString()); + } + } + + QRCode code = Encoder.encode(contents, errorCorrectionLevel, hints); + + input = code.getMatrix(); + if (input == null) { + throw new IllegalStateException(); + } + int inputWidth = input.getWidth(); + int inputHeight = input.getHeight(); + + for (int x = 0; x < inputWidth; x++) { + if (has(x, 0)) { + sideQuadSize++; + } else { + break; + } + } + + int qrWidth = inputWidth + (quietZone * 2); + int qrHeight = inputHeight + (quietZone * 2); + int outputWidth = Math.max(width, qrWidth); + int outputHeight = Math.max(height, qrHeight); + + int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight); + + int padding = 16; + + int size = multiple * inputWidth + padding * 2; + if (bitmap == null || bitmap.getWidth() != size) { + bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + } + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(0xffffffff); + Paint blackPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + blackPaint.setColor(0xff000000); + + GradientDrawable rect = new GradientDrawable(); + rect.setShape(GradientDrawable.RECTANGLE); + rect.setCornerRadii(radii); + + imageBloks = Math.round((size - 32) / 4.65f / multiple); + if (imageBloks % 2 != inputWidth % 2) { + imageBloks++; + } + imageBlockX = (inputWidth - imageBloks) / 2; + int imageSize = imageBloks * multiple - 24; + int imageX = (size - imageSize) / 2; + + for (int a = 0; a < 3; a++) { + int x, y; + if (a == 0) { + x = padding; + y = padding; + } else if (a == 1) { + x = size - sideQuadSize * multiple - padding; + y = padding; + } else { + x = padding; + y = size - sideQuadSize * multiple - padding; + } + + float r = (sideQuadSize * multiple) / 3.0f; + Arrays.fill(radii, r); + + rect.setColor(0xff000000); + rect.setBounds(x, y, x + sideQuadSize * multiple, y + sideQuadSize * multiple); + rect.draw(canvas); + + canvas.drawRect(x + multiple, y + multiple, x + (sideQuadSize - 1) * multiple, y + (sideQuadSize - 1) * multiple, blackPaint); + + r = (sideQuadSize * multiple) / 4.0f; + Arrays.fill(radii, r); + rect.setColor(0xffffffff); + rect.setBounds(x + multiple, y + multiple, x + (sideQuadSize - 1) * multiple, y + (sideQuadSize - 1) * multiple); + rect.draw(canvas); + + r = ((sideQuadSize - 2) * multiple) / 4.0f; + Arrays.fill(radii, r); + rect.setColor(0xff000000); + rect.setBounds(x + multiple * 2, y + multiple * 2, x + (sideQuadSize - 2) * multiple, y + (sideQuadSize - 2) * multiple); + rect.draw(canvas); + } + float r = multiple / 2.0f; + + for (int y = 0, outputY = padding; y < inputHeight; y++, outputY += multiple) { + for (int x = 0, outputX = padding; x < inputWidth; x++, outputX += multiple) { + if (has(x, y)) { + Arrays.fill(radii, r); + if (has(x, y - 1)) { + radii[0] = radii[1] = 0; + radii[2] = radii[3] = 0; + } + if (has(x, y + 1)) { + radii[6] = radii[7] = 0; + radii[4] = radii[5] = 0; + } + if (has(x - 1, y)) { + radii[0] = radii[1] = 0; + radii[6] = radii[7] = 0; + } + if (has(x + 1, y)) { + radii[2] = radii[3] = 0; + radii[4] = radii[5] = 0; + } + rect.setColor(0xff000000); + rect.setBounds(outputX, outputY, outputX + multiple, outputY + multiple); + rect.draw(canvas); + } else { + boolean has = false; + Arrays.fill(radii, 0); + if (has(x - 1, y - 1) && has(x - 1, y) && has(x, y - 1)) { + radii[0] = radii[1] = r; + has = true; + } + if (has(x + 1, y - 1) && has(x + 1, y) && has(x, y - 1)) { + radii[2] = radii[3] = r; + has = true; + } + if (has(x - 1, y + 1) && has(x - 1, y) && has(x, y + 1)) { + radii[6] = radii[7] = r; + has = true; + } + if (has(x + 1, y + 1) && has(x + 1, y) && has(x, y + 1)) { + radii[4] = radii[5] = r; + has = true; + } + if (has) { + canvas.drawRect(outputX, outputY, outputX + multiple, outputY + multiple, blackPaint); + rect.setColor(0xffffffff); + rect.setBounds(outputX, outputY, outputX + multiple, outputY + multiple); + rect.draw(canvas); + } + } + } + } + + Bitmap icon; + if (iconF != null) { + icon = iconF.apply(imageSize); + canvas.drawBitmap(icon, imageX, imageX, null); + icon.recycle(); + } else { + Drawable drawable = ApplicationLoader.applicationContext.getResources().getDrawable(R.mipmap.ic_launcher); + drawable.setBounds(imageX, imageX, imageX + imageSize, imageX + imageSize); + drawable.draw(canvas); + } + + canvas.setBitmap(null); + + return bitmap; + } + + private boolean has(int x, int y) { + if (x >= imageBlockX && x < imageBlockX + imageBloks && y >= imageBlockX && y < imageBlockX + imageBloks) { + return false; + } + if ((x < sideQuadSize || x >= input.getWidth() - sideQuadSize) && y < sideQuadSize) { + return false; + } + if (x < sideQuadSize && y >= input.getHeight() - sideQuadSize) { + return false; + } + return x >= 0 && y >= 0 && x < input.getWidth() && y < input.getHeight() && input.get(x, y) == 1; + } +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/BitMatrixParser.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/BitMatrixParser.java new file mode 100755 index 000000000..229869578 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/BitMatrixParser.java @@ -0,0 +1,245 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +import com.google.zxing.FormatException; +import com.google.zxing.common.BitMatrix; + +/** + * @author Sean Owen + */ +final class BitMatrixParser { + + private final BitMatrix bitMatrix; + private Version parsedVersion; + private FormatInformation parsedFormatInfo; + private boolean mirror; + + /** + * @param bitMatrix {@link BitMatrix} to parse + * @throws FormatException if dimension is not >= 21 and 1 mod 4 + */ + BitMatrixParser(BitMatrix bitMatrix) throws FormatException { + int dimension = bitMatrix.getHeight(); + if (dimension < 21 || (dimension & 0x03) != 1) { + throw FormatException.getFormatInstance(); + } + this.bitMatrix = bitMatrix; + } + + /** + *

Reads 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); + } + } + } + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DataBlock.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DataBlock.java new file mode 100755 index 000000000..8f5cdcba7 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DataBlock.java @@ -0,0 +1,122 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +/** + *

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; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java new file mode 100755 index 000000000..e60868d53 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java @@ -0,0 +1,141 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +import com.google.zxing.common.BitMatrix; + +/** + *

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); + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java new file mode 100755 index 000000000..0bfaf231c --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java @@ -0,0 +1,360 @@ +/* + * 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, + Map hints) throws FormatException { + BitSource bits = new BitSource(bytes); + StringBuilder result = new StringBuilder(50); + List byteSegments = new ArrayList<>(1); + int symbolSequence = -1; + int parityData = -1; + + try { + CharacterSetECI currentCharacterSetECI = null; + boolean fc1InEffect = false; + Mode mode; + do { + // While still another segment to read... + if (bits.available() < 4) { + // OK, assume we're done. Really, a TERMINATOR mode should have been recorded here + mode = Mode.TERMINATOR; + } else { + mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits + } + switch (mode) { + case TERMINATOR: + break; + case FNC1_FIRST_POSITION: + case FNC1_SECOND_POSITION: + // We do little with FNC1 except alter the parsed result a bit according to the spec + fc1InEffect = true; + break; + case STRUCTURED_APPEND: + if (bits.available() < 16) { + throw FormatException.getFormatInstance(); + } + // sequence number and parity is added later to the result metadata + // Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue + symbolSequence = bits.readBits(8); + parityData = bits.readBits(8); + break; + case ECI: + // Count doesn't apply to ECI + int value = parseECIValue(bits); + currentCharacterSetECI = CharacterSetECI.getCharacterSetECIByValue(value); + if (currentCharacterSetECI == null) { + throw FormatException.getFormatInstance(); + } + break; + case HANZI: + // First handle Hanzi mode which does not start with character count + // Chinese mode contains a sub set indicator right after mode indicator + int subset = bits.readBits(4); + int countHanzi = bits.readBits(mode.getCharacterCountBits(version)); + if (subset == GB2312_SUBSET) { + decodeHanziSegment(bits, result, countHanzi); + } + break; + default: + // "Normal" QR code modes: + // How many characters will follow, encoded in this mode? + int count = bits.readBits(mode.getCharacterCountBits(version)); + switch (mode) { + case NUMERIC: + decodeNumericSegment(bits, result, count); + break; + case ALPHANUMERIC: + decodeAlphanumericSegment(bits, result, count, fc1InEffect); + break; + case BYTE: + decodeByteSegment(bits, result, count, currentCharacterSetECI, byteSegments, hints); + break; + case KANJI: + decodeKanjiSegment(bits, result, count); + break; + default: + throw FormatException.getFormatInstance(); + } + break; + } + } while (mode != Mode.TERMINATOR); + } catch (IllegalArgumentException iae) { + // from readBits() calls + throw FormatException.getFormatInstance(); + } + + return new DecoderResult(bytes, + result.toString(), + byteSegments.isEmpty() ? null : byteSegments, + ecLevel == null ? null : ecLevel.toString(), + symbolSequence, + parityData); + } + + /** + * See specification GBT 18284-2000 + */ + private static void decodeHanziSegment(BitSource bits, + StringBuilder result, + int count) throws FormatException { + // Don't crash trying to read more bits than we have available. + if (count * 13 > bits.available()) { + throw FormatException.getFormatInstance(); + } + + // Each character will require 2 bytes. Read the characters as 2-byte pairs + // and decode as GB2312 afterwards + byte[] buffer = new byte[2 * count]; + int offset = 0; + while (count > 0) { + // Each 13 bits encodes a 2-byte character + int twoBytes = bits.readBits(13); + int assembledTwoBytes = ((twoBytes / 0x060) << 8) | (twoBytes % 0x060); + if (assembledTwoBytes < 0x00A00) { + // In the 0xA1A1 to 0xAAFE range + assembledTwoBytes += 0x0A1A1; + } else { + // In the 0xB0A1 to 0xFAFE range + assembledTwoBytes += 0x0A6A1; + } + buffer[offset] = (byte) ((assembledTwoBytes >> 8) & 0xFF); + buffer[offset + 1] = (byte) (assembledTwoBytes & 0xFF); + offset += 2; + count--; + } + + try { + result.append(new String(buffer, StringUtils.GB2312)); + } catch (UnsupportedEncodingException ignored) { + throw FormatException.getFormatInstance(); + } + } + + private static void decodeKanjiSegment(BitSource bits, + StringBuilder result, + int count) throws FormatException { + // Don't crash trying to read more bits than we have available. + if (count * 13 > bits.available()) { + throw FormatException.getFormatInstance(); + } + + // Each character will require 2 bytes. Read the characters as 2-byte pairs + // and decode as Shift_JIS afterwards + byte[] buffer = new byte[2 * count]; + int offset = 0; + while (count > 0) { + // Each 13 bits encodes a 2-byte character + int twoBytes = bits.readBits(13); + int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0); + if (assembledTwoBytes < 0x01F00) { + // In the 0x8140 to 0x9FFC range + assembledTwoBytes += 0x08140; + } else { + // In the 0xE040 to 0xEBBF range + assembledTwoBytes += 0x0C140; + } + buffer[offset] = (byte) (assembledTwoBytes >> 8); + buffer[offset + 1] = (byte) assembledTwoBytes; + offset += 2; + count--; + } + // Shift_JIS may not be supported in some environments: + try { + result.append(new String(buffer, StringUtils.SHIFT_JIS)); + } catch (UnsupportedEncodingException ignored) { + throw FormatException.getFormatInstance(); + } + } + + private static void decodeByteSegment(BitSource bits, + StringBuilder result, + int count, + CharacterSetECI currentCharacterSetECI, + Collection byteSegments, + Map hints) throws FormatException { + // Don't crash trying to read more bits than we have available. + if (8 * count > bits.available()) { + throw FormatException.getFormatInstance(); + } + + byte[] readBytes = new byte[count]; + for (int i = 0; i < count; i++) { + readBytes[i] = (byte) bits.readBits(8); + } + String encoding; + if (currentCharacterSetECI == null) { + // The spec isn't clear on this mode; see + // section 6.4.5: t does not say which encoding to assuming + // upon decoding. I have seen ISO-8859-1 used as well as + // Shift_JIS -- without anything like an ECI designator to + // give a hint. + encoding = StringUtils.guessEncoding(readBytes, hints); + } else { + encoding = currentCharacterSetECI.name(); + } + try { + result.append(new String(readBytes, encoding)); + } catch (UnsupportedEncodingException ignored) { + throw FormatException.getFormatInstance(); + } + byteSegments.add(readBytes); + } + + private static char toAlphaNumericChar(int value) throws FormatException { + if (value >= ALPHANUMERIC_CHARS.length) { + throw FormatException.getFormatInstance(); + } + return ALPHANUMERIC_CHARS[value]; + } + + private static void decodeAlphanumericSegment(BitSource bits, + StringBuilder result, + int count, + boolean fc1InEffect) throws FormatException { + // Read two characters at a time + int start = result.length(); + while (count > 1) { + if (bits.available() < 11) { + throw FormatException.getFormatInstance(); + } + int nextTwoCharsBits = bits.readBits(11); + result.append(toAlphaNumericChar(nextTwoCharsBits / 45)); + result.append(toAlphaNumericChar(nextTwoCharsBits % 45)); + count -= 2; + } + if (count == 1) { + // special case: one character left + if (bits.available() < 6) { + throw FormatException.getFormatInstance(); + } + result.append(toAlphaNumericChar(bits.readBits(6))); + } + // See section 6.4.8.1, 6.4.8.2 + if (fc1InEffect) { + // We need to massage the result a bit if in an FNC1 mode: + for (int i = start; i < result.length(); i++) { + if (result.charAt(i) == '%') { + if (i < result.length() - 1 && result.charAt(i + 1) == '%') { + // %% is rendered as % + result.deleteCharAt(i + 1); + } else { + // In alpha mode, % should be converted to FNC1 separator 0x1D + result.setCharAt(i, (char) 0x1D); + } + } + } + } + } + + private static void decodeNumericSegment(BitSource bits, + StringBuilder result, + int count) throws FormatException { + // Read three digits at a time + while (count >= 3) { + // Each 10 bits encodes three digits + if (bits.available() < 10) { + throw FormatException.getFormatInstance(); + } + int threeDigitsBits = bits.readBits(10); + if (threeDigitsBits >= 1000) { + throw FormatException.getFormatInstance(); + } + result.append(toAlphaNumericChar(threeDigitsBits / 100)); + result.append(toAlphaNumericChar((threeDigitsBits / 10) % 10)); + result.append(toAlphaNumericChar(threeDigitsBits % 10)); + count -= 3; + } + if (count == 2) { + // Two digits left over to read, encoded in 7 bits + if (bits.available() < 7) { + throw FormatException.getFormatInstance(); + } + int twoDigitsBits = bits.readBits(7); + if (twoDigitsBits >= 100) { + throw FormatException.getFormatInstance(); + } + result.append(toAlphaNumericChar(twoDigitsBits / 10)); + result.append(toAlphaNumericChar(twoDigitsBits % 10)); + } else if (count == 1) { + // One digit left over to read + if (bits.available() < 4) { + throw FormatException.getFormatInstance(); + } + int digitBits = bits.readBits(4); + if (digitBits >= 10) { + throw FormatException.getFormatInstance(); + } + result.append(toAlphaNumericChar(digitBits)); + } + } + + private static int parseECIValue(BitSource bits) throws FormatException { + int firstByte = bits.readBits(8); + if ((firstByte & 0x80) == 0) { + // just one byte + return firstByte & 0x7F; + } + if ((firstByte & 0xC0) == 0x80) { + // two bytes + int secondByte = bits.readBits(8); + return ((firstByte & 0x3F) << 8) | secondByte; + } + if ((firstByte & 0xE0) == 0xC0) { + // three bytes + int secondThirdBytes = bits.readBits(16); + return ((firstByte & 0x1F) << 16) | secondThirdBytes; + } + throw FormatException.getFormatInstance(); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Decoder.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Decoder.java new file mode 100755 index 000000000..7fcb7d2b0 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Decoder.java @@ -0,0 +1,189 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +import com.google.zxing.ChecksumException; +import com.google.zxing.DecodeHintType; +import com.google.zxing.FormatException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.DecoderResult; +import com.google.zxing.common.reedsolomon.GenericGF; +import com.google.zxing.common.reedsolomon.ReedSolomonDecoder; +import com.google.zxing.common.reedsolomon.ReedSolomonException; + +import java.util.Map; + +/** + *

The 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, Map hints) + throws ChecksumException, FormatException { + return decode(BitMatrix.parse(image), hints); + } + + public DecoderResult decode(BitMatrix bits) throws ChecksumException, FormatException { + return decode(bits, null); + } + + /** + *

Decodes 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, Map hints) + throws FormatException, ChecksumException { + + // Construct a parser and read version, error-correction level + BitMatrixParser parser = new BitMatrixParser(bits); + FormatException fe = null; + ChecksumException ce = null; + try { + return decode(parser, hints); + } catch (FormatException e) { + fe = e; + } catch (ChecksumException e) { + ce = e; + } + + try { + + // Revert the bit matrix + parser.remask(); + + // Will be attempting a mirrored reading of the version and format info. + parser.setMirror(true); + + // Preemptively read the version. + parser.readVersion(); + + // Preemptively read the format information. + parser.readFormatInformation(); + + /* + * Since we're here, this means we have successfully detected some kind + * of version and format information when mirrored. This is a good sign, + * that the QR code may be mirrored, and we should try once more with a + * mirrored content. + */ + // Prepare for a mirrored reading. + parser.mirror(); + + DecoderResult result = decode(parser, hints); + + // Success! Notify the caller that the code was mirrored. + result.setOther(new QRCodeDecoderMetaData(true)); + + return result; + + } catch (FormatException | ChecksumException e) { + // Throw the exception from the original reading + if (fe != null) { + throw fe; + } + throw ce; // If fe is null, this can't be + } + } + + private DecoderResult decode(BitMatrixParser parser, Map hints) + throws FormatException, ChecksumException { + Version version = parser.readVersion(); + ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel(); + + // Read codewords + byte[] codewords = parser.readCodewords(); + // Separate into data blocks + DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel); + + // Count total number of data bytes + int totalBytes = 0; + for (DataBlock dataBlock : dataBlocks) { + totalBytes += dataBlock.getNumDataCodewords(); + } + byte[] resultBytes = new byte[totalBytes]; + int resultOffset = 0; + + // Error-correct and copy data blocks together into a stream of bytes + for (DataBlock dataBlock : dataBlocks) { + byte[] codewordBytes = dataBlock.getCodewords(); + int numDataCodewords = dataBlock.getNumDataCodewords(); + correctErrors(codewordBytes, numDataCodewords); + for (int i = 0; i < numDataCodewords; i++) { + resultBytes[resultOffset++] = codewordBytes[i]; + } + } + + // Decode the contents of that stream of bytes + return DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints); + } + + /** + *

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]; + } + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java new file mode 100755 index 000000000..0f1e11387 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java @@ -0,0 +1,60 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +/** + *

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]; + } + + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/FormatInformation.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/FormatInformation.java new file mode 100755 index 000000000..95ee70156 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/FormatInformation.java @@ -0,0 +1,157 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +/** + *

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; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Mode.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Mode.java new file mode 100755 index 000000000..b7e9ab3a9 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Mode.java @@ -0,0 +1,102 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +/** + *

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; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java new file mode 100755 index 000000000..299bb6523 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +import com.google.zxing.ResultPoint; + +/** + * Meta-data container for QR Code decoding. Instances of this class may be used to convey information back to the + * decoding caller. Callers are expected to process this. + * + * @see com.google.zxing.common.DecoderResult#getOther() + */ +public final class QRCodeDecoderMetaData { + + private final boolean mirrored; + + QRCodeDecoderMetaData(boolean mirrored) { + this.mirrored = mirrored; + } + + /** + * @return true if the QR Code was mirrored. + */ + public boolean isMirrored() { + return mirrored; + } + + /** + * Apply the result points' order correction due to mirroring. + * + * @param points Array of points to apply mirror correction to. + */ + public void applyMirroredCorrection(ResultPoint[] points) { + if (!mirrored || points == null || points.length < 3) { + return; + } + ResultPoint bottomLeft = points[0]; + points[0] = points[2]; + points[2] = bottomLeft; + // No need to 'fix' top-left and alignment pattern. + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Version.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Version.java new file mode 100755 index 000000000..169e3fd36 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/decoder/Version.java @@ -0,0 +1,577 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +import com.google.zxing.FormatException; +import com.google.zxing.common.BitMatrix; + +/** + * See ISO 18004:2006 Annex D + * + * @author Sean Owen + */ +public final class Version { + + /** + * See ISO 18004:2006 Annex D. + * Element i represents the raw version bits that specify version i + 7 + */ + private static final int[] VERSION_DECODE_INFO = { + 0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, + 0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78, + 0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, + 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB, + 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, + 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B, + 0x2542E, 0x26A64, 0x27541, 0x28C69 + }; + + private static final Version[] VERSIONS = buildVersions(); + + private final int versionNumber; + private final int[] alignmentPatternCenters; + private final ECBlocks[] ecBlocks; + private final int totalCodewords; + + private Version(int versionNumber, + int[] alignmentPatternCenters, + ECBlocks... ecBlocks) { + this.versionNumber = versionNumber; + this.alignmentPatternCenters = alignmentPatternCenters; + this.ecBlocks = ecBlocks; + int total = 0; + int ecCodewords = ecBlocks[0].getECCodewordsPerBlock(); + ECB[] ecbArray = ecBlocks[0].getECBlocks(); + for (ECB ecBlock : ecbArray) { + total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords); + } + this.totalCodewords = total; + } + + public int getVersionNumber() { + return versionNumber; + } + + public int[] getAlignmentPatternCenters() { + return alignmentPatternCenters; + } + + public int getTotalCodewords() { + return totalCodewords; + } + + public int getDimensionForVersion() { + return 17 + 4 * versionNumber; + } + + public ECBlocks getECBlocksForLevel(ErrorCorrectionLevel ecLevel) { + return ecBlocks[ecLevel.ordinal()]; + } + + /** + *

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)) { + bitMatrix.setRegion(alignmentPatternCenters[y] - 2, i, 5, 5); + } + // else no o alignment patterns near the three finder patterns + } + } + + // Vertical timing pattern + bitMatrix.setRegion(6, 9, 1, dimension - 17); + // Horizontal timing pattern + bitMatrix.setRegion(9, 6, dimension - 17, 1); + + if (versionNumber > 6) { + // Version info, top right + bitMatrix.setRegion(dimension - 11, 0, 3, 6); + // Version info, bottom left + bitMatrix.setRegion(0, dimension - 11, 6, 3); + } + + return bitMatrix; + } + + /** + *

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))) + }; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/AlignmentPattern.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/AlignmentPattern.java new file mode 100755 index 000000000..96d919422 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/AlignmentPattern.java @@ -0,0 +1,59 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.detector; + +import com.google.zxing.ResultPoint; + +/** + *

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); + } + +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java new file mode 100755 index 000000000..c4f9aa0fd --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java @@ -0,0 +1,277 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.detector; + +import com.google.zxing.NotFoundException; +import com.google.zxing.ResultPointCallback; +import com.google.zxing.common.BitMatrix; + +import java.util.ArrayList; +import java.util.List; + +/** + *

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 List possibleCenters; + private final int startX; + private final int startY; + private final int width; + private final int height; + private final float moduleSize; + private final int[] crossCheckStateCount; + private final ResultPointCallback resultPointCallback; + + /** + *

Creates 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; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/Detector.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/Detector.java new file mode 100755 index 000000000..5f8787dbd --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/Detector.java @@ -0,0 +1,406 @@ +/* + * 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(Map hints) throws NotFoundException, FormatException { + + resultPointCallback = hints == null ? null : + (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); + + FinderPatternFinder finder = new FinderPatternFinder(image, resultPointCallback); + FinderPatternInfo info = finder.find(hints); + + return processFinderPatternInfo(info); + } + + protected final DetectorResult processFinderPatternInfo(FinderPatternInfo info) + throws NotFoundException, FormatException { + + FinderPattern topLeft = info.getTopLeft(); + FinderPattern topRight = info.getTopRight(); + FinderPattern bottomLeft = info.getBottomLeft(); + + float moduleSize = calculateModuleSize(topLeft, topRight, bottomLeft); + if (moduleSize < 1.0f) { + throw NotFoundException.getNotFoundInstance(); + } + int dimension = computeDimension(topLeft, topRight, bottomLeft, moduleSize); + Version provisionalVersion = Version.getProvisionalVersionForDimension(dimension); + int modulesBetweenFPCenters = provisionalVersion.getDimensionForVersion() - 7; + + AlignmentPattern alignmentPattern = null; + // Anything above version 1 has an alignment pattern + if (provisionalVersion.getAlignmentPatternCenters().length > 0) { + + // Guess where a "bottom right" finder pattern would have been + float bottomRightX = topRight.getX() - topLeft.getX() + bottomLeft.getX(); + float bottomRightY = topRight.getY() - topLeft.getY() + bottomLeft.getY(); + + // Estimate that alignment pattern is closer by 3 modules + // from "bottom right" to known top left location + float correctionToTopLeft = 1.0f - 3.0f / modulesBetweenFPCenters; + int estAlignmentX = (int) (topLeft.getX() + correctionToTopLeft * (bottomRightX - topLeft.getX())); + int estAlignmentY = (int) (topLeft.getY() + correctionToTopLeft * (bottomRightY - topLeft.getY())); + + // Kind of arbitrary -- expand search radius before giving up + for (int i = 4; i <= 16; i <<= 1) { + try { + alignmentPattern = findAlignmentInRegion(moduleSize, + estAlignmentX, + estAlignmentY, + i); + break; + } catch (NotFoundException re) { + // try next round + } + } + // If we didn't find alignment pattern... well try anyway without it + } + + PerspectiveTransform transform = + createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension); + + BitMatrix bits = sampleGrid(image, transform, dimension); + + ResultPoint[] points; + if (alignmentPattern == null) { + points = new ResultPoint[]{bottomLeft, topLeft, topRight}; + } else { + points = new ResultPoint[]{bottomLeft, topLeft, topRight, alignmentPattern}; + } + return new DetectorResult(bits, points); + } + + private static PerspectiveTransform createTransform(ResultPoint topLeft, + ResultPoint topRight, + ResultPoint bottomLeft, + ResultPoint alignmentPattern, + int dimension) { + float dimMinusThree = dimension - 3.5f; + float bottomRightX; + float bottomRightY; + float sourceBottomRightX; + float sourceBottomRightY; + if (alignmentPattern != null) { + bottomRightX = alignmentPattern.getX(); + bottomRightY = alignmentPattern.getY(); + sourceBottomRightX = dimMinusThree - 3.0f; + sourceBottomRightY = sourceBottomRightX; + } else { + // Don't have an alignment pattern, just make up the bottom-right point + bottomRightX = (topRight.getX() - topLeft.getX()) + bottomLeft.getX(); + bottomRightY = (topRight.getY() - topLeft.getY()) + bottomLeft.getY(); + sourceBottomRightX = dimMinusThree; + sourceBottomRightY = dimMinusThree; + } + + return PerspectiveTransform.quadrilateralToQuadrilateral( + 3.5f, + 3.5f, + dimMinusThree, + 3.5f, + sourceBottomRightX, + sourceBottomRightY, + 3.5f, + dimMinusThree, + topLeft.getX(), + topLeft.getY(), + topRight.getX(), + topRight.getY(), + bottomRightX, + bottomRightY, + bottomLeft.getX(), + bottomLeft.getY()); + } + + private static BitMatrix sampleGrid(BitMatrix image, + PerspectiveTransform transform, + int dimension) throws NotFoundException { + + GridSampler sampler = GridSampler.getInstance(); + return sampler.sampleGrid(image, dimension, dimension, transform); + } + + /** + *

Computes the dimension (number of modules on a size) of the QR Code based on the position + * of the finder patterns and estimated module size.

+ */ + 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: + dimension += 2; + break; + } + 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(); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPattern.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPattern.java new file mode 100755 index 000000000..a64e7c2b3 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPattern.java @@ -0,0 +1,82 @@ +/* + * 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); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java new file mode 100755 index 000000000..40fa52f1b --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java @@ -0,0 +1,675 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.detector; + +import com.google.zxing.DecodeHintType; +import com.google.zxing.NotFoundException; +import com.google.zxing.ResultPoint; +import com.google.zxing.ResultPointCallback; +import com.google.zxing.common.BitMatrix; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.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; + private static final EstimatedModuleComparator moduleComparator = new EstimatedModuleComparator(); + protected static final int MIN_SKIP = 3; // 1 pixel/module times 3 modules/center + protected static final int MAX_MODULES = 97; // support up to version 20 for mobile clients + + private final BitMatrix image; + private final List possibleCenters; + private boolean hasSkipped; + private final int[] crossCheckStateCount; + private final ResultPointCallback resultPointCallback; + + /** + *

Creates a finder that will search the image for three finder patterns.

+ * + * @param image image to search + */ + public FinderPatternFinder(BitMatrix image) { + this(image, null); + } + + public FinderPatternFinder(BitMatrix image, ResultPointCallback resultPointCallback) { + this.image = image; + this.possibleCenters = new ArrayList<>(); + this.crossCheckStateCount = new int[5]; + this.resultPointCallback = resultPointCallback; + } + + protected final BitMatrix getImage() { + return image; + } + + protected final List getPossibleCenters() { + return possibleCenters; + } + + final FinderPatternInfo find(Map hints) throws NotFoundException { + boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); + int maxI = image.getHeight(); + int maxJ = image.getWidth(); + // We are looking for black/white/black/white/black modules in + // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far + + // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the + // image, and then account for the center being 3 modules in size. This gives the smallest + // number of pixels the center could be, so skip this often. When trying harder, look for all + // QR versions regardless of how dense they are. + int iSkip = (3 * maxI) / (4 * MAX_MODULES); + if (iSkip < MIN_SKIP || tryHarder) { + iSkip = MIN_SKIP; + } + + boolean done = false; + int[] stateCount = new int[5]; + for (int i = iSkip - 1; i < maxI && !done; i += iSkip) { + // Get a row of black/white values + clearCounts(stateCount); + int currentState = 0; + for (int j = 0; j < maxJ; j++) { + if (image.get(j, i)) { + // Black pixel + if ((currentState & 1) == 1) { // Counting white pixels + currentState++; + } + stateCount[currentState]++; + } else { // White pixel + if ((currentState & 1) == 0) { // Counting black pixels + if (currentState == 4) { // A winner? + if (foundPatternCross(stateCount)) { // Yes + boolean confirmed = handlePossibleCenter(stateCount, i, j); + if (confirmed) { + // Start examining every other line. Checking each line turned out to be too + // expensive and didn't improve performance. + iSkip = 2; + if (hasSkipped) { + done = haveMultiplyConfirmedCenters(); + } else { + int rowSkip = findRowSkip(); + if (rowSkip > stateCount[2]) { + // Skip rows between row of lower confirmed center + // and top of presumed third confirmed center + // but back up a bit to get a full chance of detecting + // it, entire width of center of finder pattern + + // Skip by rowSkip, but back off by stateCount[2] (size of last center + // of pattern we saw) to be conservative, and also back off by iSkip which + // is about to be re-added + i += rowSkip - stateCount[2] - iSkip; + j = maxJ - 1; + } + } + } else { + shiftCounts2(stateCount); + currentState = 3; + continue; + } + // Clear state to start looking again + currentState = 0; + clearCounts(stateCount); + } else { // No, shift counts back by two + shiftCounts2(stateCount); + currentState = 3; + } + } else { + stateCount[++currentState]++; + } + } else { // Counting white pixels + stateCount[currentState]++; + } + } + } + if (foundPatternCross(stateCount)) { + boolean confirmed = handlePossibleCenter(stateCount, i, maxJ); + if (confirmed) { + iSkip = stateCount[0]; + if (hasSkipped) { + // Found a third one + done = haveMultiplyConfirmedCenters(); + } + } + } + } + + FinderPattern[] patternInfo = selectBestPatterns(); + ResultPoint.orderBestPatterns(patternInfo); + + return new FinderPatternInfo(patternInfo); + } + + /** + * Given a count of black/white/black/white/black pixels just seen and an end position, + * figures the location of the center of this run. + */ + private static float centerFromEnd(int[] stateCount, int end) { + return (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0f; + } + + /** + * @param stateCount count of black/white/black/white/black pixels just read + * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios + * used by finder patterns to be considered a match + */ + protected static boolean foundPatternCross(int[] stateCount) { + int totalModuleSize = 0; + for (int i = 0; i < 5; i++) { + int count = stateCount[i]; + if (count == 0) { + return false; + } + totalModuleSize += count; + } + if (totalModuleSize < 7) { + return false; + } + float moduleSize = totalModuleSize / 7.0f; + float maxVariance = moduleSize / 2.0f; + // Allow less than 50% variance from 1-1-3-1-1 proportions + return + Math.abs(moduleSize - stateCount[0]) < maxVariance && + Math.abs(moduleSize - stateCount[1]) < maxVariance && + Math.abs(3.0f * moduleSize - stateCount[2]) < 3 * maxVariance && + Math.abs(moduleSize - stateCount[3]) < maxVariance && + Math.abs(moduleSize - stateCount[4]) < maxVariance; + } + + /** + * @param stateCount count of black/white/black/white/black pixels just read + * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios + * used by finder patterns to be considered a match + */ + protected static boolean foundPatternDiagonal(int[] stateCount) { + int totalModuleSize = 0; + for (int i = 0; i < 5; i++) { + int count = stateCount[i]; + if (count == 0) { + return false; + } + totalModuleSize += count; + } + if (totalModuleSize < 7) { + return false; + } + float moduleSize = totalModuleSize / 7.0f; + float maxVariance = moduleSize / 1.333f; + // Allow less than 75% variance from 1-1-3-1-1 proportions + return + Math.abs(moduleSize - stateCount[0]) < maxVariance && + Math.abs(moduleSize - stateCount[1]) < maxVariance && + Math.abs(3.0f * moduleSize - stateCount[2]) < 3 * maxVariance && + Math.abs(moduleSize - stateCount[3]) < maxVariance && + Math.abs(moduleSize - stateCount[4]) < maxVariance; + } + + private int[] getCrossCheckStateCount() { + clearCounts(crossCheckStateCount); + return crossCheckStateCount; + } + + protected final void clearCounts(int[] counts) { + Arrays.fill(counts, 0); + } + + protected final void shiftCounts2(int[] stateCount) { + stateCount[0] = stateCount[2]; + stateCount[1] = stateCount[3]; + stateCount[2] = stateCount[4]; + stateCount[3] = 1; + stateCount[4] = 0; + } + + /** + * After a vertical and horizontal scan finds a potential finder pattern, this method + * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible + * finder pattern to see if the same proportion is detected. + * + * @param centerI row where a finder pattern was detected + * @param centerJ center of the section that appears to cross a finder pattern + * @return true if proportions are withing expected limits + */ + private boolean crossCheckDiagonal(int centerI, int centerJ) { + int[] stateCount = getCrossCheckStateCount(); + + // Start counting up, left from center finding black center mass + int i = 0; + while (centerI >= i && centerJ >= i && image.get(centerJ - i, centerI - i)) { + stateCount[2]++; + i++; + } + if (stateCount[2] == 0) { + return false; + } + + // Continue up, left finding white space + while (centerI >= i && centerJ >= i && !image.get(centerJ - i, centerI - i)) { + stateCount[1]++; + i++; + } + if (stateCount[1] == 0) { + return false; + } + + // Continue up, left finding black border + while (centerI >= i && centerJ >= i && image.get(centerJ - i, centerI - i)) { + stateCount[0]++; + i++; + } + if (stateCount[0] == 0) { + return false; + } + + int maxI = image.getHeight(); + int maxJ = image.getWidth(); + + // Now also count down, right from center + i = 1; + while (centerI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, centerI + i)) { + stateCount[2]++; + i++; + } + + while (centerI + i < maxI && centerJ + i < maxJ && !image.get(centerJ + i, centerI + i)) { + stateCount[3]++; + i++; + } + if (stateCount[3] == 0) { + return false; + } + + while (centerI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, centerI + i)) { + stateCount[4]++; + i++; + } + if (stateCount[4] == 0) { + return false; + } + + return foundPatternDiagonal(stateCount); + } + + /** + *

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.

+ * + * @param startI row where a finder pattern was detected + * @param centerJ center of the section that appears to cross a finder pattern + * @param maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @return vertical center of finder pattern, or {@link Float#NaN} if not found + */ + private float crossCheckVertical(int startI, int centerJ, int maxCount, + int originalStateCountTotal) { + BitMatrix image = this.image; + + int maxI = image.getHeight(); + int[] stateCount = getCrossCheckStateCount(); + + // Start counting up from center + int i = startI; + while (i >= 0 && image.get(centerJ, i)) { + stateCount[2]++; + i--; + } + if (i < 0) { + return Float.NaN; + } + while (i >= 0 && !image.get(centerJ, i) && stateCount[1] <= maxCount) { + stateCount[1]++; + i--; + } + // If already too many modules in this state or ran off the edge: + if (i < 0 || stateCount[1] > maxCount) { + return Float.NaN; + } + while (i >= 0 && image.get(centerJ, i) && stateCount[0] <= maxCount) { + stateCount[0]++; + i--; + } + if (stateCount[0] > maxCount) { + return Float.NaN; + } + + // Now also count down from center + i = startI + 1; + while (i < maxI && image.get(centerJ, i)) { + stateCount[2]++; + i++; + } + if (i == maxI) { + return Float.NaN; + } + while (i < maxI && !image.get(centerJ, i) && stateCount[3] < maxCount) { + stateCount[3]++; + i++; + } + if (i == maxI || stateCount[3] >= maxCount) { + return Float.NaN; + } + while (i < maxI && image.get(centerJ, i) && stateCount[4] < maxCount) { + stateCount[4]++; + i++; + } + if (stateCount[4] >= maxCount) { + return Float.NaN; + } + + // If we found a finder-pattern-like section, but its size is more than 40% different than + // the original, assume it's a false positive + int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { + return Float.NaN; + } + + return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN; + } + + /** + *

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.

+ */ + private float crossCheckHorizontal(int startJ, int centerI, int maxCount, + int originalStateCountTotal) { + BitMatrix image = this.image; + + int maxJ = image.getWidth(); + int[] stateCount = getCrossCheckStateCount(); + + int j = startJ; + while (j >= 0 && image.get(j, centerI)) { + stateCount[2]++; + j--; + } + if (j < 0) { + return Float.NaN; + } + while (j >= 0 && !image.get(j, centerI) && stateCount[1] <= maxCount) { + stateCount[1]++; + j--; + } + if (j < 0 || stateCount[1] > maxCount) { + return Float.NaN; + } + while (j >= 0 && image.get(j, centerI) && stateCount[0] <= maxCount) { + stateCount[0]++; + j--; + } + if (stateCount[0] > maxCount) { + return Float.NaN; + } + + j = startJ + 1; + while (j < maxJ && image.get(j, centerI)) { + stateCount[2]++; + j++; + } + if (j == maxJ) { + return Float.NaN; + } + while (j < maxJ && !image.get(j, centerI) && stateCount[3] < maxCount) { + stateCount[3]++; + j++; + } + if (j == maxJ || stateCount[3] >= maxCount) { + return Float.NaN; + } + while (j < maxJ && image.get(j, centerI) && stateCount[4] < maxCount) { + stateCount[4]++; + j++; + } + if (stateCount[4] >= maxCount) { + return Float.NaN; + } + + // If we found a finder-pattern-like section, but its size is significantly different than + // the original, assume it's a false positive + int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { + return Float.NaN; + } + + return foundPatternCross(stateCount) ? centerFromEnd(stateCount, j) : Float.NaN; + } + + /** + * @param stateCount reading state module counts from horizontal scan + * @param i row where finder pattern may be found + * @param j end of possible finder pattern in row + * @param pureBarcode ignored + * @return true if a finder pattern candidate was found this time + * @deprecated only exists for backwards compatibility + * @see #handlePossibleCenter(int[], int, int) + */ + @Deprecated + protected final boolean handlePossibleCenter(int[] stateCount, int i, int j, boolean pureBarcode) { + return handlePossibleCenter(stateCount, i, j); + } + + /** + *

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; + } + + /** + * Get square of distance between a and b. + */ + private static double squaredDistance(FinderPattern a, FinderPattern b) { + double x = a.getX() - b.getX(); + double y = a.getY() - b.getY(); + return x * x + y * y; + } + + /** + * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are + * those have similar module size and form a shape closer to a isosceles right triangle. + * @throws NotFoundException if 3 such finder patterns do not exist + */ + private FinderPattern[] selectBestPatterns() throws NotFoundException { + + int startSize = possibleCenters.size(); + if (startSize < 3) { + // Couldn't find enough finder patterns + throw NotFoundException.getNotFoundInstance(); + } + + Collections.sort(possibleCenters, moduleComparator); + + double distortion = Double.MAX_VALUE; + double[] squares = new double[3]; + FinderPattern[] bestPatterns = new FinderPattern[3]; + + for (int i = 0; i < possibleCenters.size() - 2; i++) { + FinderPattern fpi = possibleCenters.get(i); + float minModuleSize = fpi.getEstimatedModuleSize(); + + for (int j = i + 1; j < possibleCenters.size() - 1; j++) { + FinderPattern fpj = possibleCenters.get(j); + double squares0 = squaredDistance(fpi, fpj); + + for (int k = j + 1; k < possibleCenters.size(); k++) { + FinderPattern fpk = possibleCenters.get(k); + float maxModuleSize = fpk.getEstimatedModuleSize(); + if (maxModuleSize > minModuleSize * 1.4f) { + // module size is not similar + continue; + } + + squares[0] = squares0; + squares[1] = squaredDistance(fpj, fpk); + squares[2] = squaredDistance(fpi, fpk); + Arrays.sort(squares); + + // a^2 + b^2 = c^2 (Pythagorean theorem), and a = b (isosceles triangle). + // Since any right triangle satisfies the formula c^2 - b^2 - a^2 = 0, + // we need to check both two equal sides separately. + // The value of |c^2 - 2 * b^2| + |c^2 - 2 * a^2| increases as dissimilarity + // from isosceles right triangle. + double d = Math.abs(squares[2] - 2 * squares[1]) + Math.abs(squares[2] - 2 * squares[0]); + if (d < distortion) { + distortion = d; + bestPatterns[0] = fpi; + bestPatterns[1] = fpj; + bestPatterns[2] = fpk; + } + } + } + } + + if (distortion == Double.MAX_VALUE) { + throw NotFoundException.getNotFoundInstance(); + } + + return bestPatterns; + } + + /** + *

Orders by {@link FinderPattern#getEstimatedModuleSize()}

+ */ + private static final class EstimatedModuleComparator implements Comparator, Serializable { + @Override + public int compare(FinderPattern center1, FinderPattern center2) { + return Float.compare(center1.getEstimatedModuleSize(), center2.getEstimatedModuleSize()); + } + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPatternInfo.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPatternInfo.java new file mode 100755 index 000000000..3c3401085 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/detector/FinderPatternInfo.java @@ -0,0 +1,49 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.detector; + +/** + *

Encapsulates information about finder patterns in an image, including the location of + * the three finder patterns, and their estimated module size.

+ * + * @author Sean Owen + */ +public final class FinderPatternInfo { + + private final FinderPattern bottomLeft; + private final FinderPattern topLeft; + private final FinderPattern topRight; + + public FinderPatternInfo(FinderPattern[] patternCenters) { + this.bottomLeft = patternCenters[0]; + this.topLeft = patternCenters[1]; + this.topRight = patternCenters[2]; + } + + public FinderPattern getBottomLeft() { + return bottomLeft; + } + + public FinderPattern getTopLeft() { + return topLeft; + } + + public FinderPattern getTopRight() { + return topRight; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/BlockPair.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/BlockPair.java new file mode 100755 index 000000000..5714d9c3a --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/BlockPair.java @@ -0,0 +1,37 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +final class BlockPair { + + private final byte[] dataBytes; + private final byte[] errorCorrectionBytes; + + BlockPair(byte[] data, byte[] errorCorrection) { + dataBytes = data; + errorCorrectionBytes = errorCorrection; + } + + public byte[] getDataBytes() { + return dataBytes; + } + + public byte[] getErrorCorrectionBytes() { + return errorCorrectionBytes; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/ByteMatrix.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/ByteMatrix.java new file mode 100755 index 000000000..35f344ca3 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/ByteMatrix.java @@ -0,0 +1,99 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +import java.util.Arrays; + +/** + * JAVAPORT: The original code was a 2D array of ints, but since it only ever gets assigned + * -1, 0, and 1, I'm going to use less memory and go with bytes. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class ByteMatrix { + + private final byte[][] bytes; + private final int width; + private final int height; + + public ByteMatrix(int width, int height) { + bytes = new byte[height][width]; + this.width = width; + this.height = height; + } + + public int getHeight() { + return height; + } + + public int getWidth() { + return width; + } + + public byte get(int x, int y) { + return bytes[y][x]; + } + + /** + * @return an internal representation as bytes, in row-major order. array[y][x] represents point (x,y) + */ + public byte[][] getArray() { + return bytes; + } + + public void set(int x, int y, byte value) { + bytes[y][x] = value; + } + + public void set(int x, int y, int value) { + bytes[y][x] = (byte) value; + } + + public void set(int x, int y, boolean value) { + bytes[y][x] = (byte) (value ? 1 : 0); + } + + public void clear(byte value) { + for (byte[] aByte : bytes) { + Arrays.fill(aByte, value); + } + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(2 * width * height + 2); + for (int y = 0; y < height; ++y) { + byte[] bytesY = bytes[y]; + for (int x = 0; x < width; ++x) { + switch (bytesY[x]) { + case 0: + result.append(" 0"); + break; + case 1: + result.append(" 1"); + break; + default: + result.append(" "); + break; + } + } + result.append('\n'); + } + return result.toString(); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java new file mode 100755 index 000000000..3a80c7069 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java @@ -0,0 +1,630 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitArray; +import com.google.zxing.common.CharacterSetECI; +import com.google.zxing.common.reedsolomon.GenericGF; +import com.google.zxing.common.reedsolomon.ReedSolomonEncoder; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.google.zxing.qrcode.decoder.Mode; +import com.google.zxing.qrcode.decoder.Version; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported from C++ + */ +public final class Encoder { + + // The original table is defined in the table 5 of JISX0510:2004 (p.19). + private static final int[] ALPHANUMERIC_TABLE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f + 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f + }; + + static final String DEFAULT_BYTE_MODE_ENCODING = "ISO-8859-1"; + + private Encoder() { + } + + // The mask penalty calculation is complicated. See Table 21 of JISX0510:2004 (p.45) for details. + // Basically it applies four rules and summate all penalties. + private static int calculateMaskPenalty(ByteMatrix matrix) { + return MaskUtil.applyMaskPenaltyRule1(matrix) + + MaskUtil.applyMaskPenaltyRule2(matrix) + + MaskUtil.applyMaskPenaltyRule3(matrix) + + MaskUtil.applyMaskPenaltyRule4(matrix); + } + + /** + * @param content text to encode + * @param ecLevel error correction level to use + * @return {@link QRCode} representing the encoded QR code + * @throws WriterException if encoding can't succeed, because of for example invalid content + * or configuration + */ + public static QRCode encode(String content, ErrorCorrectionLevel ecLevel) throws WriterException { + return encode(content, ecLevel, null); + } + + public static QRCode encode(String content, + ErrorCorrectionLevel ecLevel, + Map hints) throws WriterException { + + // Determine what character encoding has been specified by the caller, if any + String encoding = DEFAULT_BYTE_MODE_ENCODING; + boolean hasEncodingHint = hints != null && hints.containsKey(EncodeHintType.CHARACTER_SET); + if (hasEncodingHint) { + encoding = hints.get(EncodeHintType.CHARACTER_SET).toString(); + } + + // Pick an encoding mode appropriate for the content. Note that this will not attempt to use + // multiple modes / segments even if that were more efficient. Twould be nice. + Mode mode = chooseMode(content, encoding); + + // This will store the header information, like mode and + // length, as well as "header" segments like an ECI segment. + BitArray headerBits = new BitArray(); + + // Append ECI segment if applicable + if (mode == Mode.BYTE && hasEncodingHint) { + CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding); + if (eci != null) { + appendECI(eci, headerBits); + } + } + + // Append the FNC1 mode header for GS1 formatted data if applicable + boolean hasGS1FormatHint = hints != null && hints.containsKey(EncodeHintType.GS1_FORMAT); + if (hasGS1FormatHint && Boolean.parseBoolean(hints.get(EncodeHintType.GS1_FORMAT).toString())) { + // GS1 formatted codes are prefixed with a FNC1 in first position mode header + appendModeInfo(Mode.FNC1_FIRST_POSITION, headerBits); + } + + // (With ECI in place,) Write the mode marker + appendModeInfo(mode, headerBits); + + // Collect data within the main segment, separately, to count its size if needed. Don't add it to + // main payload yet. + BitArray dataBits = new BitArray(); + appendBytes(content, mode, dataBits, encoding); + + Version version; + if (hints != null && hints.containsKey(EncodeHintType.QR_VERSION)) { + int versionNumber = Integer.parseInt(hints.get(EncodeHintType.QR_VERSION).toString()); + version = Version.getVersionForNumber(versionNumber); + int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, version); + if (!willFit(bitsNeeded, version, ecLevel)) { + throw new WriterException("Data too big for requested version"); + } + } else { + version = recommendVersion(ecLevel, mode, headerBits, dataBits); + } + + BitArray headerAndDataBits = new BitArray(); + headerAndDataBits.appendBitArray(headerBits); + // Find "length" of main segment and write it + int numLetters = mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length(); + appendLengthInfo(numLetters, version, mode, headerAndDataBits); + // Put data together into the overall payload + headerAndDataBits.appendBitArray(dataBits); + + Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel); + int numDataBytes = version.getTotalCodewords() - ecBlocks.getTotalECCodewords(); + + // Terminate the bits properly. + terminateBits(numDataBytes, headerAndDataBits); + + // Interleave data bits with error correction code. + BitArray finalBits = interleaveWithECBytes(headerAndDataBits, + version.getTotalCodewords(), + numDataBytes, + ecBlocks.getNumBlocks()); + + QRCode qrCode = new QRCode(); + + qrCode.setECLevel(ecLevel); + qrCode.setMode(mode); + qrCode.setVersion(version); + + // Choose the mask pattern and set to "qrCode". + int dimension = version.getDimensionForVersion(); + ByteMatrix matrix = new ByteMatrix(dimension, dimension); + + // Enable manual selection of the pattern to be used via hint + int maskPattern = -1; + if (hints != null && hints.containsKey(EncodeHintType.QR_MASK_PATTERN)) { + int hintMaskPattern = Integer.parseInt(hints.get(EncodeHintType.QR_MASK_PATTERN).toString()); + maskPattern = QRCode.isValidMaskPattern(hintMaskPattern) ? hintMaskPattern : -1; + } + + if (maskPattern == -1) { + maskPattern = chooseMaskPattern(finalBits, ecLevel, version, matrix); + } + qrCode.setMaskPattern(maskPattern); + + // Build the matrix and set it to "qrCode". + MatrixUtil.buildMatrix(finalBits, ecLevel, version, maskPattern, matrix); + qrCode.setMatrix(matrix); + + return qrCode; + } + + /** + * Decides the smallest version of QR code that will contain all of the provided data. + * + * @throws WriterException if the data cannot fit in any version + */ + private static Version recommendVersion(ErrorCorrectionLevel ecLevel, + Mode mode, + BitArray headerBits, + BitArray dataBits) throws WriterException { + // Hard part: need to know version to know how many bits length takes. But need to know how many + // bits it takes to know version. First we take a guess at version by assuming version will be + // the minimum, 1: + int provisionalBitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, Version.getVersionForNumber(1)); + Version provisionalVersion = chooseVersion(provisionalBitsNeeded, ecLevel); + + // Use that guess to calculate the right version. I am still not sure this works in 100% of cases. + int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, provisionalVersion); + return chooseVersion(bitsNeeded, ecLevel); + } + + private static int calculateBitsNeeded(Mode mode, + BitArray headerBits, + BitArray dataBits, + Version version) { + return headerBits.getSize() + mode.getCharacterCountBits(version) + dataBits.getSize(); + } + + /** + * @return the code point of the table used in alphanumeric mode or + * -1 if there is no corresponding code in the table. + */ + static int getAlphanumericCode(int code) { + if (code < ALPHANUMERIC_TABLE.length) { + return ALPHANUMERIC_TABLE[code]; + } + return -1; + } + + public static Mode chooseMode(String content) { + return chooseMode(content, null); + } + + /** + * Choose the best mode by examining the content. Note that 'encoding' is used as a hint; + * if it is Shift_JIS, and the input is only double-byte Kanji, then we return {@link Mode#KANJI}. + */ + private static Mode chooseMode(String content, String encoding) { + if ("Shift_JIS".equals(encoding) && isOnlyDoubleByteKanji(content)) { + // Choose Kanji mode if all input are double-byte characters + return Mode.KANJI; + } + boolean hasNumeric = false; + boolean hasAlphanumeric = false; + for (int i = 0; i < content.length(); ++i) { + char c = content.charAt(i); + if (c >= '0' && c <= '9') { + hasNumeric = true; + } else if (getAlphanumericCode(c) != -1) { + hasAlphanumeric = true; + } else { + return Mode.BYTE; + } + } + if (hasAlphanumeric) { + return Mode.ALPHANUMERIC; + } + if (hasNumeric) { + return Mode.NUMERIC; + } + return Mode.BYTE; + } + + private static boolean isOnlyDoubleByteKanji(String content) { + byte[] bytes; + try { + bytes = content.getBytes("Shift_JIS"); + } catch (UnsupportedEncodingException ignored) { + return false; + } + int length = bytes.length; + if (length % 2 != 0) { + return false; + } + for (int i = 0; i < length; i += 2) { + int byte1 = bytes[i] & 0xFF; + if ((byte1 < 0x81 || byte1 > 0x9F) && (byte1 < 0xE0 || byte1 > 0xEB)) { + return false; + } + } + return true; + } + + private static int chooseMaskPattern(BitArray bits, + ErrorCorrectionLevel ecLevel, + Version version, + ByteMatrix matrix) throws WriterException { + + int minPenalty = Integer.MAX_VALUE; // Lower penalty is better. + int bestMaskPattern = -1; + // We try all mask patterns to choose the best one. + for (int maskPattern = 0; maskPattern < QRCode.NUM_MASK_PATTERNS; maskPattern++) { + MatrixUtil.buildMatrix(bits, ecLevel, version, maskPattern, matrix); + int penalty = calculateMaskPenalty(matrix); + if (penalty < minPenalty) { + minPenalty = penalty; + bestMaskPattern = maskPattern; + } + } + return bestMaskPattern; + } + + private static Version chooseVersion(int numInputBits, ErrorCorrectionLevel ecLevel) throws WriterException { + for (int versionNum = 1; versionNum <= 40; versionNum++) { + Version version = Version.getVersionForNumber(versionNum); + if (willFit(numInputBits, version, ecLevel)) { + return version; + } + } + throw new WriterException("Data too big"); + } + + /** + * @return true if the number of input bits will fit in a code with the specified version and + * error correction level. + */ + private static boolean willFit(int numInputBits, Version version, ErrorCorrectionLevel ecLevel) { + // In the following comments, we use numbers of Version 7-H. + // numBytes = 196 + int numBytes = version.getTotalCodewords(); + // getNumECBytes = 130 + Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel); + int numEcBytes = ecBlocks.getTotalECCodewords(); + // getNumDataBytes = 196 - 130 = 66 + int numDataBytes = numBytes - numEcBytes; + int totalInputBytes = (numInputBits + 7) / 8; + return numDataBytes >= totalInputBytes; + } + + /** + * Terminate bits as described in 8.4.8 and 8.4.9 of JISX0510:2004 (p.24). + */ + static void terminateBits(int numDataBytes, BitArray bits) throws WriterException { + int capacity = numDataBytes * 8; + if (bits.getSize() > capacity) { + throw new WriterException("data bits cannot fit in the QR Code" + bits.getSize() + " > " + + capacity); + } + for (int i = 0; i < 4 && bits.getSize() < capacity; ++i) { + bits.appendBit(false); + } + // Append termination bits. See 8.4.8 of JISX0510:2004 (p.24) for details. + // If the last byte isn't 8-bit aligned, we'll add padding bits. + int numBitsInLastByte = bits.getSize() & 0x07; + if (numBitsInLastByte > 0) { + for (int i = numBitsInLastByte; i < 8; i++) { + bits.appendBit(false); + } + } + // If we have more space, we'll fill the space with padding patterns defined in 8.4.9 (p.24). + int numPaddingBytes = numDataBytes - bits.getSizeInBytes(); + for (int i = 0; i < numPaddingBytes; ++i) { + bits.appendBits((i & 0x01) == 0 ? 0xEC : 0x11, 8); + } + if (bits.getSize() != capacity) { + throw new WriterException("Bits size does not equal capacity"); + } + } + + /** + * Get number of data bytes and number of error correction bytes for block id "blockID". Store + * the result in "numDataBytesInBlock", and "numECBytesInBlock". See table 12 in 8.5.1 of + * JISX0510:2004 (p.30) + */ + static void getNumDataBytesAndNumECBytesForBlockID(int numTotalBytes, + int numDataBytes, + int numRSBlocks, + int blockID, + int[] numDataBytesInBlock, + int[] numECBytesInBlock) throws WriterException { + if (blockID >= numRSBlocks) { + throw new WriterException("Block ID too large"); + } + // numRsBlocksInGroup2 = 196 % 5 = 1 + int numRsBlocksInGroup2 = numTotalBytes % numRSBlocks; + // numRsBlocksInGroup1 = 5 - 1 = 4 + int numRsBlocksInGroup1 = numRSBlocks - numRsBlocksInGroup2; + // numTotalBytesInGroup1 = 196 / 5 = 39 + int numTotalBytesInGroup1 = numTotalBytes / numRSBlocks; + // numTotalBytesInGroup2 = 39 + 1 = 40 + int numTotalBytesInGroup2 = numTotalBytesInGroup1 + 1; + // numDataBytesInGroup1 = 66 / 5 = 13 + int numDataBytesInGroup1 = numDataBytes / numRSBlocks; + // numDataBytesInGroup2 = 13 + 1 = 14 + int numDataBytesInGroup2 = numDataBytesInGroup1 + 1; + // numEcBytesInGroup1 = 39 - 13 = 26 + int numEcBytesInGroup1 = numTotalBytesInGroup1 - numDataBytesInGroup1; + // numEcBytesInGroup2 = 40 - 14 = 26 + int numEcBytesInGroup2 = numTotalBytesInGroup2 - numDataBytesInGroup2; + // Sanity checks. + // 26 = 26 + if (numEcBytesInGroup1 != numEcBytesInGroup2) { + throw new WriterException("EC bytes mismatch"); + } + // 5 = 4 + 1. + if (numRSBlocks != numRsBlocksInGroup1 + numRsBlocksInGroup2) { + throw new WriterException("RS blocks mismatch"); + } + // 196 = (13 + 26) * 4 + (14 + 26) * 1 + if (numTotalBytes != + ((numDataBytesInGroup1 + numEcBytesInGroup1) * + numRsBlocksInGroup1) + + ((numDataBytesInGroup2 + numEcBytesInGroup2) * + numRsBlocksInGroup2)) { + throw new WriterException("Total bytes mismatch"); + } + + if (blockID < numRsBlocksInGroup1) { + numDataBytesInBlock[0] = numDataBytesInGroup1; + numECBytesInBlock[0] = numEcBytesInGroup1; + } else { + numDataBytesInBlock[0] = numDataBytesInGroup2; + numECBytesInBlock[0] = numEcBytesInGroup2; + } + } + + /** + * Interleave "bits" with corresponding error correction bytes. On success, store the result in + * "result". The interleave rule is complicated. See 8.6 of JISX0510:2004 (p.37) for details. + */ + static BitArray interleaveWithECBytes(BitArray bits, + int numTotalBytes, + int numDataBytes, + int numRSBlocks) throws WriterException { + + // "bits" must have "getNumDataBytes" bytes of data. + if (bits.getSizeInBytes() != numDataBytes) { + throw new WriterException("Number of bits and data bytes does not match"); + } + + // Step 1. Divide data bytes into blocks and generate error correction bytes for them. We'll + // store the divided data bytes blocks and error correction bytes blocks into "blocks". + int dataBytesOffset = 0; + int maxNumDataBytes = 0; + int maxNumEcBytes = 0; + + // Since, we know the number of reedsolmon blocks, we can initialize the vector with the number. + Collection blocks = new ArrayList<>(numRSBlocks); + + for (int i = 0; i < numRSBlocks; ++i) { + int[] numDataBytesInBlock = new int[1]; + int[] numEcBytesInBlock = new int[1]; + getNumDataBytesAndNumECBytesForBlockID( + numTotalBytes, numDataBytes, numRSBlocks, i, + numDataBytesInBlock, numEcBytesInBlock); + + int size = numDataBytesInBlock[0]; + byte[] dataBytes = new byte[size]; + bits.toBytes(8 * dataBytesOffset, dataBytes, 0, size); + byte[] ecBytes = generateECBytes(dataBytes, numEcBytesInBlock[0]); + blocks.add(new BlockPair(dataBytes, ecBytes)); + + maxNumDataBytes = Math.max(maxNumDataBytes, size); + maxNumEcBytes = Math.max(maxNumEcBytes, ecBytes.length); + dataBytesOffset += numDataBytesInBlock[0]; + } + if (numDataBytes != dataBytesOffset) { + throw new WriterException("Data bytes does not match offset"); + } + + BitArray result = new BitArray(); + + // First, place data blocks. + for (int i = 0; i < maxNumDataBytes; ++i) { + for (BlockPair block : blocks) { + byte[] dataBytes = block.getDataBytes(); + if (i < dataBytes.length) { + result.appendBits(dataBytes[i], 8); + } + } + } + // Then, place error correction blocks. + for (int i = 0; i < maxNumEcBytes; ++i) { + for (BlockPair block : blocks) { + byte[] ecBytes = block.getErrorCorrectionBytes(); + if (i < ecBytes.length) { + result.appendBits(ecBytes[i], 8); + } + } + } + if (numTotalBytes != result.getSizeInBytes()) { // Should be same. + throw new WriterException("Interleaving error: " + numTotalBytes + " and " + + result.getSizeInBytes() + " differ."); + } + + return result; + } + + static byte[] generateECBytes(byte[] dataBytes, int numEcBytesInBlock) { + int numDataBytes = dataBytes.length; + int[] toEncode = new int[numDataBytes + numEcBytesInBlock]; + for (int i = 0; i < numDataBytes; i++) { + toEncode[i] = dataBytes[i] & 0xFF; + } + new ReedSolomonEncoder(GenericGF.QR_CODE_FIELD_256).encode(toEncode, numEcBytesInBlock); + + byte[] ecBytes = new byte[numEcBytesInBlock]; + for (int i = 0; i < numEcBytesInBlock; i++) { + ecBytes[i] = (byte) toEncode[numDataBytes + i]; + } + return ecBytes; + } + + /** + * Append mode info. On success, store the result in "bits". + */ + static void appendModeInfo(Mode mode, BitArray bits) { + bits.appendBits(mode.getBits(), 4); + } + + + /** + * Append length info. On success, store the result in "bits". + */ + static void appendLengthInfo(int numLetters, Version version, Mode mode, BitArray bits) throws WriterException { + int numBits = mode.getCharacterCountBits(version); + if (numLetters >= (1 << numBits)) { + throw new WriterException(numLetters + " is bigger than " + ((1 << numBits) - 1)); + } + bits.appendBits(numLetters, numBits); + } + + /** + * Append "bytes" in "mode" mode (encoding) into "bits". On success, store the result in "bits". + */ + static void appendBytes(String content, + Mode mode, + BitArray bits, + String encoding) throws WriterException { + switch (mode) { + case NUMERIC: + appendNumericBytes(content, bits); + break; + case ALPHANUMERIC: + appendAlphanumericBytes(content, bits); + break; + case BYTE: + append8BitBytes(content, bits, encoding); + break; + case KANJI: + appendKanjiBytes(content, bits); + break; + default: + throw new WriterException("Invalid mode: " + mode); + } + } + + static void appendNumericBytes(CharSequence content, BitArray bits) { + int length = content.length(); + int i = 0; + while (i < length) { + int num1 = content.charAt(i) - '0'; + if (i + 2 < length) { + // Encode three numeric letters in ten bits. + int num2 = content.charAt(i + 1) - '0'; + int num3 = content.charAt(i + 2) - '0'; + bits.appendBits(num1 * 100 + num2 * 10 + num3, 10); + i += 3; + } else if (i + 1 < length) { + // Encode two numeric letters in seven bits. + int num2 = content.charAt(i + 1) - '0'; + bits.appendBits(num1 * 10 + num2, 7); + i += 2; + } else { + // Encode one numeric letter in four bits. + bits.appendBits(num1, 4); + i++; + } + } + } + + static void appendAlphanumericBytes(CharSequence content, BitArray bits) throws WriterException { + int length = content.length(); + int i = 0; + while (i < length) { + int code1 = getAlphanumericCode(content.charAt(i)); + if (code1 == -1) { + throw new WriterException(); + } + if (i + 1 < length) { + int code2 = getAlphanumericCode(content.charAt(i + 1)); + if (code2 == -1) { + throw new WriterException(); + } + // Encode two alphanumeric letters in 11 bits. + bits.appendBits(code1 * 45 + code2, 11); + i += 2; + } else { + // Encode one alphanumeric letter in six bits. + bits.appendBits(code1, 6); + i++; + } + } + } + + static void append8BitBytes(String content, BitArray bits, String encoding) + throws WriterException { + byte[] bytes; + try { + bytes = content.getBytes(encoding); + } catch (UnsupportedEncodingException uee) { + throw new WriterException(uee); + } + for (byte b : bytes) { + bits.appendBits(b, 8); + } + } + + static void appendKanjiBytes(String content, BitArray bits) throws WriterException { + byte[] bytes; + try { + bytes = content.getBytes("Shift_JIS"); + } catch (UnsupportedEncodingException uee) { + throw new WriterException(uee); + } + if (bytes.length % 2 != 0) { + throw new WriterException("Kanji byte size not even"); + } + int maxI = bytes.length - 1; // bytes.length must be even + for (int i = 0; i < maxI; i += 2) { + int byte1 = bytes[i] & 0xFF; + int byte2 = bytes[i + 1] & 0xFF; + int code = (byte1 << 8) | byte2; + int subtracted = -1; + if (code >= 0x8140 && code <= 0x9ffc) { + subtracted = code - 0x8140; + } else if (code >= 0xe040 && code <= 0xebbf) { + subtracted = code - 0xc140; + } + if (subtracted == -1) { + throw new WriterException("Invalid byte sequence"); + } + int encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff); + bits.appendBits(encoded, 13); + } + } + + private static void appendECI(CharacterSetECI eci, BitArray bits) { + bits.appendBits(Mode.ECI.getBits(), 4); + // This is correct for values up to 127, which is all we need now. + bits.appendBits(eci.getValue(), 8); + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java new file mode 100755 index 000000000..11d780469 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java @@ -0,0 +1,222 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +/** + * @author Satoru Takabayashi + * @author Daniel Switkin + * @author Sean Owen + */ +final class MaskUtil { + + // Penalty weights from section 6.8.2.1 + private static final int N1 = 3; + private static final int N2 = 3; + private static final int N3 = 40; + private static final int N4 = 10; + + private MaskUtil() { + // do nothing + } + + /** + * Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and + * give penalty to them. Example: 00000 or 11111. + */ + static int applyMaskPenaltyRule1(ByteMatrix matrix) { + return applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false); + } + + /** + * Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give + * penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a + * penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block. + */ + static int applyMaskPenaltyRule2(ByteMatrix matrix) { + int penalty = 0; + byte[][] array = matrix.getArray(); + int width = matrix.getWidth(); + int height = matrix.getHeight(); + for (int y = 0; y < height - 1; y++) { + byte[] arrayY = array[y]; + for (int x = 0; x < width - 1; x++) { + int value = arrayY[x]; + if (value == arrayY[x + 1] && value == array[y + 1][x] && value == array[y + 1][x + 1]) { + penalty++; + } + } + } + return N2 * penalty; + } + + /** + * Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4 + * starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we + * find patterns like 000010111010000, we give penalty once. + */ + static int applyMaskPenaltyRule3(ByteMatrix matrix) { + int numPenalties = 0; + byte[][] array = matrix.getArray(); + int width = matrix.getWidth(); + int height = matrix.getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + byte[] arrayY = array[y]; // We can at least optimize this access + if (x + 6 < width && + arrayY[x] == 1 && + arrayY[x + 1] == 0 && + arrayY[x + 2] == 1 && + arrayY[x + 3] == 1 && + arrayY[x + 4] == 1 && + arrayY[x + 5] == 0 && + arrayY[x + 6] == 1 && + (isWhiteHorizontal(arrayY, x - 4, x) || isWhiteHorizontal(arrayY, x + 7, x + 11))) { + numPenalties++; + } + if (y + 6 < height && + array[y][x] == 1 && + array[y + 1][x] == 0 && + array[y + 2][x] == 1 && + array[y + 3][x] == 1 && + array[y + 4][x] == 1 && + array[y + 5][x] == 0 && + array[y + 6][x] == 1 && + (isWhiteVertical(array, x, y - 4, y) || isWhiteVertical(array, x, y + 7, y + 11))) { + numPenalties++; + } + } + } + return numPenalties * N3; + } + + private static boolean isWhiteHorizontal(byte[] rowArray, int from, int to) { + from = Math.max(from, 0); + to = Math.min(to, rowArray.length); + for (int i = from; i < to; i++) { + if (rowArray[i] == 1) { + return false; + } + } + return true; + } + + private static boolean isWhiteVertical(byte[][] array, int col, int from, int to) { + from = Math.max(from, 0); + to = Math.min(to, array.length); + for (int i = from; i < to; i++) { + if (array[i][col] == 1) { + return false; + } + } + return true; + } + + /** + * Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give + * penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance. + */ + static int applyMaskPenaltyRule4(ByteMatrix matrix) { + int numDarkCells = 0; + byte[][] array = matrix.getArray(); + int width = matrix.getWidth(); + int height = matrix.getHeight(); + for (int y = 0; y < height; y++) { + byte[] arrayY = array[y]; + for (int x = 0; x < width; x++) { + if (arrayY[x] == 1) { + numDarkCells++; + } + } + } + int numTotalCells = matrix.getHeight() * matrix.getWidth(); + int fivePercentVariances = Math.abs(numDarkCells * 2 - numTotalCells) * 10 / numTotalCells; + return fivePercentVariances * N4; + } + + /** + * Return the mask bit for "getMaskPattern" at "x" and "y". See 8.8 of JISX0510:2004 for mask + * pattern conditions. + */ + static boolean getDataMaskBit(int maskPattern, int x, int y) { + int intermediate; + int temp; + switch (maskPattern) { + case 0: + intermediate = (y + x) & 0x1; + break; + case 1: + intermediate = y & 0x1; + break; + case 2: + intermediate = x % 3; + break; + case 3: + intermediate = (y + x) % 3; + break; + case 4: + intermediate = ((y / 2) + (x / 3)) & 0x1; + break; + case 5: + temp = y * x; + intermediate = (temp & 0x1) + (temp % 3); + break; + case 6: + temp = y * x; + intermediate = ((temp & 0x1) + (temp % 3)) & 0x1; + break; + case 7: + temp = y * x; + intermediate = ((temp % 3) + ((y + x) & 0x1)) & 0x1; + break; + default: + throw new IllegalArgumentException("Invalid mask pattern: " + maskPattern); + } + return intermediate == 0; + } + + /** + * Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both + * vertical and horizontal orders respectively. + */ + private static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, boolean isHorizontal) { + int penalty = 0; + int iLimit = isHorizontal ? matrix.getHeight() : matrix.getWidth(); + int jLimit = isHorizontal ? matrix.getWidth() : matrix.getHeight(); + byte[][] array = matrix.getArray(); + for (int i = 0; i < iLimit; i++) { + int numSameBitCells = 0; + int prevBit = -1; + for (int j = 0; j < jLimit; j++) { + int bit = isHorizontal ? array[i][j] : array[j][i]; + if (bit == prevBit) { + numSameBitCells++; + } else { + if (numSameBitCells >= 5) { + penalty += N1 + (numSameBitCells - 5); + } + numSameBitCells = 1; // Include the cell itself. + prevBit = bit; + } + } + if (numSameBitCells >= 5) { + penalty += N1 + (numSameBitCells - 5); + } + } + return penalty; + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/MatrixUtil.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/MatrixUtil.java new file mode 100755 index 000000000..561284a35 --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/MatrixUtil.java @@ -0,0 +1,476 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +import com.google.zxing.WriterException; +import com.google.zxing.common.BitArray; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.google.zxing.qrcode.decoder.Version; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported from C++ + */ +final class MatrixUtil { + + private static final int[][] POSITION_DETECTION_PATTERN = { + {1, 1, 1, 1, 1, 1, 1}, + {1, 0, 0, 0, 0, 0, 1}, + {1, 0, 1, 1, 1, 0, 1}, + {1, 0, 1, 1, 1, 0, 1}, + {1, 0, 1, 1, 1, 0, 1}, + {1, 0, 0, 0, 0, 0, 1}, + {1, 1, 1, 1, 1, 1, 1}, + }; + + private static final int[][] POSITION_ADJUSTMENT_PATTERN = { + {1, 1, 1, 1, 1}, + {1, 0, 0, 0, 1}, + {1, 0, 1, 0, 1}, + {1, 0, 0, 0, 1}, + {1, 1, 1, 1, 1}, + }; + + // From Appendix E. Table 1, JIS0510X:2004 (p 71). The table was double-checked by komatsu. + private static final int[][] POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = { + {-1, -1, -1, -1, -1, -1, -1}, // Version 1 + { 6, 18, -1, -1, -1, -1, -1}, // Version 2 + { 6, 22, -1, -1, -1, -1, -1}, // Version 3 + { 6, 26, -1, -1, -1, -1, -1}, // Version 4 + { 6, 30, -1, -1, -1, -1, -1}, // Version 5 + { 6, 34, -1, -1, -1, -1, -1}, // Version 6 + { 6, 22, 38, -1, -1, -1, -1}, // Version 7 + { 6, 24, 42, -1, -1, -1, -1}, // Version 8 + { 6, 26, 46, -1, -1, -1, -1}, // Version 9 + { 6, 28, 50, -1, -1, -1, -1}, // Version 10 + { 6, 30, 54, -1, -1, -1, -1}, // Version 11 + { 6, 32, 58, -1, -1, -1, -1}, // Version 12 + { 6, 34, 62, -1, -1, -1, -1}, // Version 13 + { 6, 26, 46, 66, -1, -1, -1}, // Version 14 + { 6, 26, 48, 70, -1, -1, -1}, // Version 15 + { 6, 26, 50, 74, -1, -1, -1}, // Version 16 + { 6, 30, 54, 78, -1, -1, -1}, // Version 17 + { 6, 30, 56, 82, -1, -1, -1}, // Version 18 + { 6, 30, 58, 86, -1, -1, -1}, // Version 19 + { 6, 34, 62, 90, -1, -1, -1}, // Version 20 + { 6, 28, 50, 72, 94, -1, -1}, // Version 21 + { 6, 26, 50, 74, 98, -1, -1}, // Version 22 + { 6, 30, 54, 78, 102, -1, -1}, // Version 23 + { 6, 28, 54, 80, 106, -1, -1}, // Version 24 + { 6, 32, 58, 84, 110, -1, -1}, // Version 25 + { 6, 30, 58, 86, 114, -1, -1}, // Version 26 + { 6, 34, 62, 90, 118, -1, -1}, // Version 27 + { 6, 26, 50, 74, 98, 122, -1}, // Version 28 + { 6, 30, 54, 78, 102, 126, -1}, // Version 29 + { 6, 26, 52, 78, 104, 130, -1}, // Version 30 + { 6, 30, 56, 82, 108, 134, -1}, // Version 31 + { 6, 34, 60, 86, 112, 138, -1}, // Version 32 + { 6, 30, 58, 86, 114, 142, -1}, // Version 33 + { 6, 34, 62, 90, 118, 146, -1}, // Version 34 + { 6, 30, 54, 78, 102, 126, 150}, // Version 35 + { 6, 24, 50, 76, 102, 128, 154}, // Version 36 + { 6, 28, 54, 80, 106, 132, 158}, // Version 37 + { 6, 32, 58, 84, 110, 136, 162}, // Version 38 + { 6, 26, 54, 82, 110, 138, 166}, // Version 39 + { 6, 30, 58, 86, 114, 142, 170}, // Version 40 + }; + + // Type info cells at the left top corner. + private static final int[][] TYPE_INFO_COORDINATES = { + {8, 0}, + {8, 1}, + {8, 2}, + {8, 3}, + {8, 4}, + {8, 5}, + {8, 7}, + {8, 8}, + {7, 8}, + {5, 8}, + {4, 8}, + {3, 8}, + {2, 8}, + {1, 8}, + {0, 8}, + }; + + // From Appendix D in JISX0510:2004 (p. 67) + private static final int VERSION_INFO_POLY = 0x1f25; // 1 1111 0010 0101 + + // From Appendix C in JISX0510:2004 (p.65). + private static final int TYPE_INFO_POLY = 0x537; + private static final int TYPE_INFO_MASK_PATTERN = 0x5412; + + private MatrixUtil() { + // do nothing + } + + // Set all cells to -1. -1 means that the cell is empty (not set yet). + // + // JAVAPORT: We shouldn't need to do this at all. The code should be rewritten to begin encoding + // with the ByteMatrix initialized all to zero. + static void clearMatrix(ByteMatrix matrix) { + matrix.clear((byte) -1); + } + + // Build 2D matrix of QR Code from "dataBits" with "ecLevel", "version" and "getMaskPattern". On + // success, store the result in "matrix" and return true. + static void buildMatrix(BitArray dataBits, + ErrorCorrectionLevel ecLevel, + Version version, + int maskPattern, + ByteMatrix matrix) throws WriterException { + clearMatrix(matrix); + embedBasicPatterns(version, matrix); + // Type information appear with any version. + embedTypeInfo(ecLevel, maskPattern, matrix); + // Version info appear if version >= 7. + maybeEmbedVersionInfo(version, matrix); + // Data should be embedded at end. + embedDataBits(dataBits, maskPattern, matrix); + } + + // Embed basic patterns. On success, modify the matrix and return true. + // The basic patterns are: + // - Position detection patterns + // - Timing patterns + // - Dark dot at the left bottom corner + // - Position adjustment patterns, if need be + static void embedBasicPatterns(Version version, ByteMatrix matrix) throws WriterException { + // Let's get started with embedding big squares at corners. + embedPositionDetectionPatternsAndSeparators(matrix); + // Then, embed the dark dot at the left bottom corner. + embedDarkDotAtLeftBottomCorner(matrix); + + // Position adjustment patterns appear if version >= 2. + maybeEmbedPositionAdjustmentPatterns(version, matrix); + // Timing patterns should be embedded after position adj. patterns. + embedTimingPatterns(matrix); + } + + // Embed type information. On success, modify the matrix. + static void embedTypeInfo(ErrorCorrectionLevel ecLevel, int maskPattern, ByteMatrix matrix) + throws WriterException { + BitArray typeInfoBits = new BitArray(); + makeTypeInfoBits(ecLevel, maskPattern, typeInfoBits); + + for (int i = 0; i < typeInfoBits.getSize(); ++i) { + // Place bits in LSB to MSB order. LSB (least significant bit) is the last value in + // "typeInfoBits". + boolean bit = typeInfoBits.get(typeInfoBits.getSize() - 1 - i); + + // Type info bits at the left top corner. See 8.9 of JISX0510:2004 (p.46). + int[] coordinates = TYPE_INFO_COORDINATES[i]; + int x1 = coordinates[0]; + int y1 = coordinates[1]; + matrix.set(x1, y1, bit); + + if (i < 8) { + // Right top corner. + int x2 = matrix.getWidth() - i - 1; + int y2 = 8; + matrix.set(x2, y2, bit); + } else { + // Left bottom corner. + int x2 = 8; + int y2 = matrix.getHeight() - 7 + (i - 8); + matrix.set(x2, y2, bit); + } + } + } + + // Embed version information if need be. On success, modify the matrix and return true. + // See 8.10 of JISX0510:2004 (p.47) for how to embed version information. + static void maybeEmbedVersionInfo(Version version, ByteMatrix matrix) throws WriterException { + if (version.getVersionNumber() < 7) { // Version info is necessary if version >= 7. + return; // Don't need version info. + } + BitArray versionInfoBits = new BitArray(); + makeVersionInfoBits(version, versionInfoBits); + + int bitIndex = 6 * 3 - 1; // It will decrease from 17 to 0. + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 3; ++j) { + // Place bits in LSB (least significant bit) to MSB order. + boolean bit = versionInfoBits.get(bitIndex); + bitIndex--; + // Left bottom corner. + matrix.set(i, matrix.getHeight() - 11 + j, bit); + // Right bottom corner. + matrix.set(matrix.getHeight() - 11 + j, i, bit); + } + } + } + + // Embed "dataBits" using "getMaskPattern". On success, modify the matrix and return true. + // For debugging purposes, it skips masking process if "getMaskPattern" is -1. + // See 8.7 of JISX0510:2004 (p.38) for how to embed data bits. + static void embedDataBits(BitArray dataBits, int maskPattern, ByteMatrix matrix) + throws WriterException { + int bitIndex = 0; + int direction = -1; + // Start from the right bottom cell. + int x = matrix.getWidth() - 1; + int y = matrix.getHeight() - 1; + while (x > 0) { + // Skip the vertical timing pattern. + if (x == 6) { + x -= 1; + } + while (y >= 0 && y < matrix.getHeight()) { + for (int i = 0; i < 2; ++i) { + int xx = x - i; + // Skip the cell if it's not empty. + if (!isEmpty(matrix.get(xx, y))) { + continue; + } + boolean bit; + if (bitIndex < dataBits.getSize()) { + bit = dataBits.get(bitIndex); + ++bitIndex; + } else { + // Padding bit. If there is no bit left, we'll fill the left cells with 0, as described + // in 8.4.9 of JISX0510:2004 (p. 24). + bit = false; + } + + // Skip masking if mask_pattern is -1. + if (maskPattern != -1 && MaskUtil.getDataMaskBit(maskPattern, xx, y)) { + bit = !bit; + } + matrix.set(xx, y, bit); + } + y += direction; + } + direction = -direction; // Reverse the direction. + y += direction; + x -= 2; // Move to the left. + } + // All bits should be consumed. + if (bitIndex != dataBits.getSize()) { + throw new WriterException("Not all bits consumed: " + bitIndex + '/' + dataBits.getSize()); + } + } + + // Return the position of the most significant bit set (to one) in the "value". The most + // significant bit is position 32. If there is no bit set, return 0. Examples: + // - findMSBSet(0) => 0 + // - findMSBSet(1) => 1 + // - findMSBSet(255) => 8 + static int findMSBSet(int value) { + return 32 - Integer.numberOfLeadingZeros(value); + } + + // Calculate BCH (Bose-Chaudhuri-Hocquenghem) code for "value" using polynomial "poly". The BCH + // code is used for encoding type information and version information. + // Example: Calculation of version information of 7. + // f(x) is created from 7. + // - 7 = 000111 in 6 bits + // - f(x) = x^2 + x^1 + x^0 + // g(x) is given by the standard (p. 67) + // - g(x) = x^12 + x^11 + x^10 + x^9 + x^8 + x^5 + x^2 + 1 + // Multiply f(x) by x^(18 - 6) + // - f'(x) = f(x) * x^(18 - 6) + // - f'(x) = x^14 + x^13 + x^12 + // Calculate the remainder of f'(x) / g(x) + // x^2 + // __________________________________________________ + // g(x) )x^14 + x^13 + x^12 + // x^14 + x^13 + x^12 + x^11 + x^10 + x^7 + x^4 + x^2 + // -------------------------------------------------- + // x^11 + x^10 + x^7 + x^4 + x^2 + // + // The remainder is x^11 + x^10 + x^7 + x^4 + x^2 + // Encode it in binary: 110010010100 + // The return value is 0xc94 (1100 1001 0100) + // + // Since all coefficients in the polynomials are 1 or 0, we can do the calculation by bit + // operations. We don't care if coefficients are positive or negative. + static int calculateBCHCode(int value, int poly) { + if (poly == 0) { + throw new IllegalArgumentException("0 polynomial"); + } + // If poly is "1 1111 0010 0101" (version info poly), msbSetInPoly is 13. We'll subtract 1 + // from 13 to make it 12. + int msbSetInPoly = findMSBSet(poly); + value <<= msbSetInPoly - 1; + // Do the division business using exclusive-or operations. + while (findMSBSet(value) >= msbSetInPoly) { + value ^= poly << (findMSBSet(value) - msbSetInPoly); + } + // Now the "value" is the remainder (i.e. the BCH code) + return value; + } + + // Make bit vector of type information. On success, store the result in "bits" and return true. + // Encode error correction level and mask pattern. See 8.9 of + // JISX0510:2004 (p.45) for details. + static void makeTypeInfoBits(ErrorCorrectionLevel ecLevel, int maskPattern, BitArray bits) + throws WriterException { + if (!QRCode.isValidMaskPattern(maskPattern)) { + throw new WriterException("Invalid mask pattern"); + } + int typeInfo = (ecLevel.getBits() << 3) | maskPattern; + bits.appendBits(typeInfo, 5); + + int bchCode = calculateBCHCode(typeInfo, TYPE_INFO_POLY); + bits.appendBits(bchCode, 10); + + BitArray maskBits = new BitArray(); + maskBits.appendBits(TYPE_INFO_MASK_PATTERN, 15); + bits.xor(maskBits); + + if (bits.getSize() != 15) { // Just in case. + throw new WriterException("should not happen but we got: " + bits.getSize()); + } + } + + // Make bit vector of version information. On success, store the result in "bits" and return true. + // See 8.10 of JISX0510:2004 (p.45) for details. + static void makeVersionInfoBits(Version version, BitArray bits) throws WriterException { + bits.appendBits(version.getVersionNumber(), 6); + int bchCode = calculateBCHCode(version.getVersionNumber(), VERSION_INFO_POLY); + bits.appendBits(bchCode, 12); + + if (bits.getSize() != 18) { // Just in case. + throw new WriterException("should not happen but we got: " + bits.getSize()); + } + } + + // Check if "value" is empty. + private static boolean isEmpty(int value) { + return value == -1; + } + + private static void embedTimingPatterns(ByteMatrix matrix) { + // -8 is for skipping position detection patterns (size 7), and two horizontal/vertical + // separation patterns (size 1). Thus, 8 = 7 + 1. + for (int i = 8; i < matrix.getWidth() - 8; ++i) { + int bit = (i + 1) % 2; + // Horizontal line. + if (isEmpty(matrix.get(i, 6))) { + matrix.set(i, 6, bit); + } + // Vertical line. + if (isEmpty(matrix.get(6, i))) { + matrix.set(6, i, bit); + } + } + } + + // Embed the lonely dark dot at left bottom corner. JISX0510:2004 (p.46) + private static void embedDarkDotAtLeftBottomCorner(ByteMatrix matrix) throws WriterException { + if (matrix.get(8, matrix.getHeight() - 8) == 0) { + throw new WriterException(); + } + matrix.set(8, matrix.getHeight() - 8, 1); + } + + private static void embedHorizontalSeparationPattern(int xStart, + int yStart, + ByteMatrix matrix) throws WriterException { + for (int x = 0; x < 8; ++x) { + if (!isEmpty(matrix.get(xStart + x, yStart))) { + throw new WriterException(); + } + matrix.set(xStart + x, yStart, 0); + } + } + + private static void embedVerticalSeparationPattern(int xStart, + int yStart, + ByteMatrix matrix) throws WriterException { + for (int y = 0; y < 7; ++y) { + if (!isEmpty(matrix.get(xStart, yStart + y))) { + throw new WriterException(); + } + matrix.set(xStart, yStart + y, 0); + } + } + + private static void embedPositionAdjustmentPattern(int xStart, int yStart, ByteMatrix matrix) { + for (int y = 0; y < 5; ++y) { + int[] patternY = POSITION_ADJUSTMENT_PATTERN[y]; + for (int x = 0; x < 5; ++x) { + matrix.set(xStart + x, yStart + y, patternY[x]); + } + } + } + + private static void embedPositionDetectionPattern(int xStart, int yStart, ByteMatrix matrix) { + for (int y = 0; y < 7; ++y) { + int[] patternY = POSITION_DETECTION_PATTERN[y]; + for (int x = 0; x < 7; ++x) { + matrix.set(xStart + x, yStart + y, patternY[x]); + } + } + } + + // Embed position detection patterns and surrounding vertical/horizontal separators. + private static void embedPositionDetectionPatternsAndSeparators(ByteMatrix matrix) throws WriterException { + // Embed three big squares at corners. + int pdpWidth = POSITION_DETECTION_PATTERN[0].length; + // Left top corner. + embedPositionDetectionPattern(0, 0, matrix); + // Right top corner. + embedPositionDetectionPattern(matrix.getWidth() - pdpWidth, 0, matrix); + // Left bottom corner. + embedPositionDetectionPattern(0, matrix.getWidth() - pdpWidth, matrix); + + // Embed horizontal separation patterns around the squares. + int hspWidth = 8; + // Left top corner. + embedHorizontalSeparationPattern(0, hspWidth - 1, matrix); + // Right top corner. + embedHorizontalSeparationPattern(matrix.getWidth() - hspWidth, + hspWidth - 1, matrix); + // Left bottom corner. + embedHorizontalSeparationPattern(0, matrix.getWidth() - hspWidth, matrix); + + // Embed vertical separation patterns around the squares. + int vspSize = 7; + // Left top corner. + embedVerticalSeparationPattern(vspSize, 0, matrix); + // Right top corner. + embedVerticalSeparationPattern(matrix.getHeight() - vspSize - 1, 0, matrix); + // Left bottom corner. + embedVerticalSeparationPattern(vspSize, matrix.getHeight() - vspSize, + matrix); + } + + // Embed position adjustment patterns if need be. + private static void maybeEmbedPositionAdjustmentPatterns(Version version, ByteMatrix matrix) { + if (version.getVersionNumber() < 2) { // The patterns appear if version >= 2 + return; + } + int index = version.getVersionNumber() - 1; + int[] coordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index]; + for (int y : coordinates) { + if (y >= 0) { + for (int x : coordinates) { + if (x >= 0 && isEmpty(matrix.get(x, y))) { + // If the cell is unset, we embed the position adjustment pattern here. + // -2 is necessary since the x/y coordinates point to the center of the pattern, not the + // left top corner. + embedPositionAdjustmentPattern(x - 2, y - 2, matrix); + } + } + } + } + } + +} diff --git a/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/QRCode.java b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/QRCode.java new file mode 100755 index 000000000..b560de92f --- /dev/null +++ b/TMessagesProj/src/main/java/com/google/zxing/qrcode/encoder/QRCode.java @@ -0,0 +1,108 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.google.zxing.qrcode.decoder.Mode; +import com.google.zxing.qrcode.decoder.Version; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported from C++ + */ +public final class QRCode { + + public static final int NUM_MASK_PATTERNS = 8; + + private Mode mode; + private ErrorCorrectionLevel ecLevel; + private Version version; + private int maskPattern; + private ByteMatrix matrix; + + public QRCode() { + maskPattern = -1; + } + + public Mode getMode() { + return mode; + } + + public ErrorCorrectionLevel getECLevel() { + return ecLevel; + } + + public Version getVersion() { + return version; + } + + public int getMaskPattern() { + return maskPattern; + } + + public ByteMatrix getMatrix() { + return matrix; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(200); + result.append("<<\n"); + result.append(" mode: "); + result.append(mode); + result.append("\n ecLevel: "); + result.append(ecLevel); + result.append("\n version: "); + result.append(version); + result.append("\n maskPattern: "); + result.append(maskPattern); + if (matrix == null) { + result.append("\n matrix: null\n"); + } else { + result.append("\n matrix:\n"); + result.append(matrix); + } + result.append(">>\n"); + return result.toString(); + } + + public void setMode(Mode value) { + mode = value; + } + + public void setECLevel(ErrorCorrectionLevel value) { + ecLevel = value; + } + + public void setVersion(Version version) { + this.version = version; + } + + public void setMaskPattern(int value) { + maskPattern = value; + } + + public void setMatrix(ByteMatrix value) { + matrix = value; + } + + // Check if "mask_pattern" is valid. + public static boolean isValidMaskPattern(int maskPattern) { + return maskPattern >= 0 && maskPattern < NUM_MASK_PATTERNS; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java index a06305caf..4b22d1446 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java @@ -2971,6 +2971,8 @@ public class AndroidUtilities { String link = data.toString(); if (link.startsWith("tg://proxy") || link.startsWith("tg://socks") || + link.startsWith("http://t.me/proxy?") || + link.startsWith("http://t.me/socks?") || link.startsWith("https://t.me/proxy?") || link.startsWith("https://t.me/socks?") || link.startsWith(VMESS_PROTOCOL) || diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java index 030b7e58f..030fe0812 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java @@ -170,9 +170,13 @@ import java.util.List; import java.util.Locale; import cn.hutool.core.util.StrUtil; +import kotlin.Unit; +import tw.nekomimi.nekogram.BottomBuilder; import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.parts.ArticleTransKt; import tw.nekomimi.nekogram.transtale.TranslateDb; +import tw.nekomimi.nekogram.utils.AlertUtil; +import tw.nekomimi.nekogram.utils.ProxyUtil; import static org.telegram.messenger.MessageObject.POSITION_FLAG_BOTTOM; import static org.telegram.messenger.MessageObject.POSITION_FLAG_LEFT; @@ -1182,48 +1186,54 @@ public class ArticleViewer implements NotificationCenter.NotificationCenterDeleg linkSheet = null; } - BottomSheet.Builder builder = new BottomSheet.Builder(parentActivity); - builder.setTitle(urlFinal); - builder.setItems(new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, (dialog, which) -> { - if (parentActivity == null) { - return; - } - if (which == 0) { - int index; - if ((index = urlFinal.lastIndexOf('#')) != -1) { - String webPageUrl; - if (!TextUtils.isEmpty(adapter[0].currentPage.cached_page.url)) { - webPageUrl = adapter[0].currentPage.cached_page.url.toLowerCase(); - } else { - webPageUrl = adapter[0].currentPage.url.toLowerCase(); - } - String anchor; - try { - anchor = URLDecoder.decode(urlFinal.substring(index + 1), "UTF-8"); - } catch (Exception ignore) { - anchor = ""; - } - if (urlFinal.toLowerCase().contains(webPageUrl)) { - if (TextUtils.isEmpty(anchor)) { - layoutManager[0].scrollToPositionWithOffset(0, 0); - checkScrollAnimated(); - } else { - scrollToAnchor(anchor); + BottomBuilder builder = new BottomBuilder(parentActivity); + builder.addTitle(urlFinal); + builder.addItems( + new String[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy), LocaleController.getString("ShareQRCode", R.string.ShareQRCode)}, + new Integer[]{R.drawable.baseline_open_in_browser_24, R.drawable.baseline_content_copy_24,R.drawable.wallet_qr }, (which, text, cell) -> { + if (parentActivity == null) return Unit.INSTANCE; + if (which == 0 || which == 2) { + int index; + if ((index = urlFinal.lastIndexOf('#')) != -1) { + String webPageUrl; + if (!TextUtils.isEmpty(adapter[0].currentPage.cached_page.url)) { + webPageUrl = adapter[0].currentPage.cached_page.url.toLowerCase(); + } else { + webPageUrl = adapter[0].currentPage.url.toLowerCase(); + } + String anchor; + try { + anchor = URLDecoder.decode(urlFinal.substring(index + 1), "UTF-8"); + } catch (Exception ignore) { + anchor = ""; + } + if (urlFinal.toLowerCase().contains(webPageUrl)) { + if (TextUtils.isEmpty(anchor)) { + layoutManager[0].scrollToPositionWithOffset(0, 0); + checkScrollAnimated(); + } else { + scrollToAnchor(anchor); + } + return Unit.INSTANCE; + } } - return; + if (which == 0 ) { + Browser.openUrl(parentActivity, urlFinal); + } else { + ProxyUtil.showQrDialog(parentActivity, urlFinal); + } + } else if (which == 1) { + String url = urlFinal; + if (url.startsWith("mailto:")) { + url = url.substring(7); + } else if (url.startsWith("tel:")) { + url = url.substring(4); + } + AndroidUtilities.addToClipboard(url); + AlertUtil.showToast(LocaleController.getString("LinkCopied", R.string.LinkCopied)); } - } - Browser.openUrl(parentActivity, urlFinal); - } else if (which == 1) { - String url = urlFinal; - if (url.startsWith("mailto:")) { - url = url.substring(7); - } else if (url.startsWith("tel:")) { - url = url.substring(4); - } - AndroidUtilities.addToClipboard(url); - } - }); + return Unit.INSTANCE; + }); BottomSheet sheet = builder.create(); showDialog(sheet); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java index 1ef78505b..3fac5138d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CameraScanActivity.java @@ -670,14 +670,7 @@ public class CameraScanActivity extends BaseFragment implements Camera.PreviewCa } else { source = new PlanarYUVLuminanceSource(data, size.getWidth(), size.getHeight(), x, y, side, side, false); } - Result result = null; - try { - result = qrReader.decode(new BinaryBitmap(new GlobalHistogramBinarizer(source))); - } catch (NotFoundException e) { - try { - result = qrReader.decode(new BinaryBitmap(new GlobalHistogramBinarizer(source.invert()))); - } catch (NotFoundException ignore) {} - } + Result result = qrReader.decode(new BinaryBitmap(new GlobalHistogramBinarizer(source))); if (result == null) { onNoQrFound(); return null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java index 34b8b16dd..5492c7afc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelAdminLogActivity.java @@ -115,8 +115,11 @@ import java.io.FileWriter; import java.util.ArrayList; import java.util.HashMap; +import kotlin.Unit; +import tw.nekomimi.nekogram.BottomBuilder; import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.utils.AlertUtil; +import tw.nekomimi.nekogram.utils.ProxyUtil; public class ChannelAdminLogActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { @@ -1547,7 +1550,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio int checkLoadCount; if (scroll) { checkLoadCount = 25; - } else { + } else { checkLoadCount = 5; } if (firstVisibleItem <= checkLoadCount && !loading && !endReached) { @@ -2023,21 +2026,29 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio } else { final String urlFinal = ((URLSpan) url).getURL(); if (longPress) { - BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); - builder.setTitle(urlFinal); - builder.setItems(new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, (dialog, which) -> { - if (which == 0) { - Browser.openUrl(getParentActivity(), urlFinal, true); - } else if (which == 1) { - String url1 = urlFinal; - if (url1.startsWith("mailto:")) { - url1 = url1.substring(7); - } else if (url1.startsWith("tel:")) { - url1 = url1.substring(4); - } - AndroidUtilities.addToClipboard(url1); - } - }); + BottomBuilder builder = new BottomBuilder(getParentActivity()); + builder.addTitle(urlFinal); + builder.addItems( + new String[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy), LocaleController.getString("ShareQRCode", R.string.ShareQRCode)}, + new Integer[]{R.drawable.baseline_open_in_browser_24, R.drawable.baseline_content_copy_24,R.drawable.wallet_qr }, (which, text, __) -> { + if (which == 0 || which == 2) { + if (which == 0) { + Browser.openUrl(getParentActivity(), urlFinal); + } else { + ProxyUtil.showQrDialog(getParentActivity(), urlFinal); + } + } else if (which == 1) { + String url1 = urlFinal; + if (url1.startsWith("mailto:")) { + url1 = url1.substring(7); + } else if (url1.startsWith("tel:")) { + url1 = url1.substring(4); + } + AndroidUtilities.addToClipboard(url1); + AlertUtil.showToast(LocaleController.getString("LinkCopied", R.string.LinkCopied)); + } + return Unit.INSTANCE; + }); showDialog(builder.create()); } else { if (url instanceof URLSpanReplacement) { @@ -2639,7 +2650,7 @@ public class ChannelAdminLogActivity extends BaseFragment implements Notificatio themeDescriptions.add(new ThemeDescription(avatarContainer != null ? avatarContainer.getTimeItem() : null, 0, null, null, null, null, Theme.key_chat_secretTimerBackground)); themeDescriptions.add(new ThemeDescription(avatarContainer != null ? avatarContainer.getTimeItem() : null, 0, null, null, null, null, Theme.key_chat_secretTimerText)); - + return themeDescriptions; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 183fe5d9b..7503c2065 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -255,6 +255,7 @@ import tw.nekomimi.nekogram.parts.PollTransUpdatesKt; import tw.nekomimi.nekogram.transtale.Translator; import tw.nekomimi.nekogram.utils.AlertUtil; import tw.nekomimi.nekogram.utils.PGPUtil; +import tw.nekomimi.nekogram.utils.ProxyUtil; @SuppressWarnings("unchecked") public class ChatActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, DialogsActivity.DialogsActivityDelegate, LocationActivity.LocationActivityDelegate, ChatAttachAlertDocumentLayout.DocumentSelectActivityDelegate { @@ -21202,9 +21203,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not messageCell.setChecked(true, groupedMessages == null, animated); } - private void openClickableLink(String str) { + private void openClickableLink(String str, boolean qr) { if (str.startsWith("@")) { String username = str.substring(1).toLowerCase(); + if (qr) { + ProxyUtil.showQrDialog(getParentActivity(), "https://t.me/" + username); + return; + } if (currentChat != null && !TextUtils.isEmpty(currentChat.username) && username.equals(currentChat.username.toLowerCase()) || currentUser != null && !TextUtils.isEmpty(currentUser.username) && username.equals(currentUser.username.toLowerCase())) { Bundle args = new Bundle(); @@ -21238,6 +21243,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not presentFragment(fragment); } } else { + if (qr) { + ProxyUtil.showQrDialog(getParentActivity(), str); + return; + } if (AndroidUtilities.shouldShowUrlInAlert(str)) { AlertsCreator.showOpenUrlAlert(ChatActivity.this, str, true, true, true); } else { @@ -21353,42 +21362,54 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not }, 500); } else { if (longPress) { - BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); - builder.setTitle(str); - builder.setItems(new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, (dialog, which) -> { - if (which == 0) { - openClickableLink(str); + BottomBuilder builder = new BottomBuilder(getParentActivity()); + builder.addTitle(str); + builder.addItems( + new String[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy), str.startsWith("#") || str.startsWith("$") ? null : LocaleController.getString("ShareQRCode", R.string.ShareQRCode)}, + new Integer[]{R.drawable.baseline_open_in_browser_24, R.drawable.baseline_content_copy_24,R.drawable.wallet_qr }, (which, text, __) -> { + if (which == 0 || which == 2) { + openClickableLink(str, which == 2); + } else if (which == 1) { AndroidUtilities.addToClipboard(str); } + return Unit.INSTANCE; }); showDialog(builder.create()); } else { - openClickableLink(str); + openClickableLink(str, false); } } } else { final String urlFinal = ((URLSpan) url).getURL(); if (longPress) { - BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); - builder.setTitle(urlFinal); - builder.setItems(new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, (dialog, which) -> { - if (which == 0) { - if (AndroidUtilities.shouldShowUrlInAlert(urlFinal)) { - AlertsCreator.showOpenUrlAlert(ChatActivity.this, urlFinal, true, true, false); - } else { - Browser.openUrl(getParentActivity(), urlFinal, inlineReturn == 0, false); - } - } else if (which == 1) { - String url1 = urlFinal; - if (url1.startsWith("mailto:")) { - url1 = url1.substring(7); - } else if (url1.startsWith("tel:")) { - url1 = url1.substring(4); - } - AndroidUtilities.addToClipboard(url1); - } - }); + BottomBuilder builder = new BottomBuilder(getParentActivity()); + builder.addTitle(urlFinal); + builder.addItems( + new String[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy), LocaleController.getString("ShareQRCode", R.string.ShareQRCode)}, + new Integer[]{R.drawable.baseline_open_in_browser_24, R.drawable.baseline_content_copy_24,R.drawable.wallet_qr }, (which, text, __) -> { + if (which == 0 || which == 2) { + if (which == 0) { + if (AndroidUtilities.shouldShowUrlInAlert(urlFinal)) { + AlertsCreator.showOpenUrlAlert(ChatActivity.this, urlFinal, true, true, false); + } else { + Browser.openUrl(getParentActivity(), urlFinal, inlineReturn == 0, false); + } + } else { + ProxyUtil.showQrDialog(getParentActivity(), urlFinal); + } + } else if (which == 1) { + String url1 = urlFinal; + if (url1.startsWith("mailto:")) { + url1 = url1.substring(7); + } else if (url1.startsWith("tel:")) { + url1 = url1.substring(4); + } + AndroidUtilities.addToClipboard(url1); + AlertUtil.showToast(LocaleController.getString("LinkCopied", R.string.LinkCopied)); + } + return Unit.INSTANCE; + }); showDialog(builder.create()); } else { boolean punycode = false; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java index 28809c6e7..395ae05e4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java @@ -3140,7 +3140,7 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente } } } else if (position == recentActionsRow) { - actionCell.setText(LocaleController.getString("EventLog", R.string.EventLog), null, R.drawable.baseline_content_paste_18, false); + actionCell.setText(LocaleController.getString("EventLog", R.string.EventLog), null, R.drawable.baseline_content_copy_24, false); } else if (position == addNew2Row) { actionCell.setText(LocaleController.getString("ChannelInviteViaLink", R.string.ChannelInviteViaLink), null, R.drawable.profile_link, true); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java index c8f19b77c..69a8191de 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java @@ -3256,7 +3256,7 @@ public class ChatActivityEnterView extends FrameLayout implements NotificationCe Intent intent = new Intent(); - if (NekoConfig.openPGPKeyId != 0L && save) + if (NekoConfig.openPGPKeyId > 0L && save) intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, NekoConfig.openPGPKeyId); signComment(intent, save); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/QRCodeBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/QRCodeBottomSheet.java index bdc36eb2e..f52ceefeb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/QRCodeBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/QRCodeBottomSheet.java @@ -104,7 +104,10 @@ public class QRCodeBottomSheet extends BottomSheet { public Bitmap createQR(Context context, String key, Bitmap oldBitmap) { try { - ProxyUtil.createQRCode(key, 768); + HashMap hints = new HashMap<>(); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); + hints.put(EncodeHintType.MARGIN, 0); + return new QRCodeWriter().encode(key, BarcodeFormat.QR_CODE, 768, 768, hints, oldBitmap, context); } catch (Exception e) { FileLog.e(e); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java index cfd268266..8f1415677 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SharedMediaLayout.java @@ -98,6 +98,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import kotlin.Unit; +import tw.nekomimi.nekogram.BottomBuilder; +import tw.nekomimi.nekogram.utils.AlertUtil; +import tw.nekomimi.nekogram.utils.ProxyUtil; + @SuppressWarnings("unchecked") public class SharedMediaLayout extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { @@ -3073,21 +3078,29 @@ public class SharedMediaLayout extends FrameLayout implements NotificationCenter @Override public void onLinkPress(String urlFinal, boolean longPress) { if (longPress) { - BottomSheet.Builder builder = new BottomSheet.Builder(profileActivity.getParentActivity()); - builder.setTitle(urlFinal); - builder.setItems(new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, (dialog, which) -> { - if (which == 0) { - openUrl(urlFinal); - } else if (which == 1) { - String url = urlFinal; - if (url.startsWith("mailto:")) { - url = url.substring(7); - } else if (url.startsWith("tel:")) { - url = url.substring(4); - } - AndroidUtilities.addToClipboard(url); - } - }); + BottomBuilder builder = new BottomBuilder(profileActivity.getParentActivity()); + builder.addTitle(urlFinal); + builder.addItems( + new String[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy), LocaleController.getString("ShareQRCode", R.string.ShareQRCode)}, + new Integer[]{R.drawable.baseline_open_in_browser_24, R.drawable.baseline_content_copy_24,R.drawable.wallet_qr }, (which, text, __) -> { + if (which == 0 || which == 2) { + if (which == 0) { + openUrl(urlFinal); + } else { + ProxyUtil.showQrDialog(profileActivity.getParentActivity(), urlFinal); + } + } else if (which == 1) { + String url1 = urlFinal; + if (url1.startsWith("mailto:")) { + url1 = url1.substring(7); + } else if (url1.startsWith("tel:")) { + url1 = url1.substring(4); + } + AndroidUtilities.addToClipboard(url1); + AlertUtil.showToast(LocaleController.getString("LinkCopied", R.string.LinkCopied)); + } + return Unit.INSTANCE; + }); profileActivity.showDialog(builder.create()); } else { openUrl(urlFinal); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java index 6384e48a3..f671c5033 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java @@ -16,6 +16,7 @@ import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; @@ -45,6 +46,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.DocumentObject; import org.telegram.messenger.MediaDataController; import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLoader; @@ -53,9 +55,11 @@ import org.telegram.messenger.FileRefController; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaDataController; +import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.messenger.SvgHelper; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; @@ -739,6 +743,15 @@ public class StickersAlert extends BottomSheet implements NotificationCenter.Not FileLog.e(e); } } else if (id == 3) { + for (int i = 0, size = gridView.getChildCount(); i < size; i++) { + final View child = gridView.getChildAt(i); + if (child instanceof StickerEmojiCell) { + Bitmap bitmap = ((StickerEmojiCell) child).getImageView().getImageReceiver().getBitmap(); + if (bitmap == null) continue; + ProxyUtil.showQrDialog(getContext(), stickersUrl, imageSize -> Bitmap.createScaledBitmap(bitmap,imageSize,imageSize, true)); + return; + } + } ProxyUtil.showQrDialog(getContext(), stickersUrl); } else if (id == menu_archive) { dismiss(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/FilteredSearchView.java b/TMessagesProj/src/main/java/org/telegram/ui/FilteredSearchView.java index 143a943a9..b7412be1b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/FilteredSearchView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/FilteredSearchView.java @@ -75,6 +75,11 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; +import kotlin.Unit; +import tw.nekomimi.nekogram.BottomBuilder; +import tw.nekomimi.nekogram.utils.AlertUtil; +import tw.nekomimi.nekogram.utils.ProxyUtil; + public class FilteredSearchView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { public RecyclerListView recyclerListView; @@ -1078,21 +1083,29 @@ public class FilteredSearchView extends FrameLayout implements NotificationCente @Override public void onLinkPress(String urlFinal, boolean longPress) { if (longPress) { - BottomSheet.Builder builder = new BottomSheet.Builder(parentActivity); - builder.setTitle(urlFinal); - builder.setItems(new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, (dialog, which) -> { - if (which == 0) { - openUrl(urlFinal); - } else if (which == 1) { - String url = urlFinal; - if (url.startsWith("mailto:")) { - url = url.substring(7); - } else if (url.startsWith("tel:")) { - url = url.substring(4); - } - AndroidUtilities.addToClipboard(url); - } - }); + BottomBuilder builder = new BottomBuilder(parentActivity); + builder.addTitle(urlFinal); + builder.addItems( + new String[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy), LocaleController.getString("ShareQRCode", R.string.ShareQRCode)}, + new Integer[]{R.drawable.baseline_open_in_browser_24, R.drawable.baseline_content_copy_24,R.drawable.wallet_qr }, (which, text, __) -> { + if (which == 0 || which == 2) { + if (which == 0) { + openUrl(urlFinal); + } else { + ProxyUtil.showQrDialog(parentActivity, urlFinal); + } + } else if (which == 1) { + String url1 = urlFinal; + if (url1.startsWith("mailto:")) { + url1 = url1.substring(7); + } else if (url1.startsWith("tel:")) { + url1 = url1.substring(4); + } + AndroidUtilities.addToClipboard(url1); + AlertUtil.showToast(LocaleController.getString("LinkCopied", R.string.LinkCopied)); + } + return Unit.INSTANCE; + }); parentFragment.showDialog(builder.create()); } else { openUrl(urlFinal); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java index 8990e08ff..ea5f15c4a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java @@ -96,6 +96,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import kotlin.Unit; +import tw.nekomimi.nekogram.BottomBuilder; +import tw.nekomimi.nekogram.utils.AlertUtil; +import tw.nekomimi.nekogram.utils.ProxyUtil; + @SuppressWarnings("unchecked") public class MediaActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { @@ -2074,21 +2079,29 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No @Override public void onLinkPress(String urlFinal, boolean longPress) { if (longPress) { - BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); - builder.setTitle(urlFinal); - builder.setItems(new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, (dialog, which) -> { - if (which == 0) { - openUrl(urlFinal); - } else if (which == 1) { - String url = urlFinal; - if (url.startsWith("mailto:")) { - url = url.substring(7); - } else if (url.startsWith("tel:")) { - url = url.substring(4); - } - AndroidUtilities.addToClipboard(url); - } - }); + BottomBuilder builder = new BottomBuilder(getParentActivity()); + builder.addTitle(urlFinal); + builder.addItems( + new String[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy), LocaleController.getString("ShareQRCode", R.string.ShareQRCode)}, + new Integer[]{R.drawable.baseline_open_in_browser_24, R.drawable.baseline_content_copy_24,R.drawable.wallet_qr }, (which, text, __) -> { + if (which == 0 || which == 2) { + if (which == 0) { + openUrl(urlFinal); + } else { + ProxyUtil.showQrDialog(getParentActivity(), urlFinal); + } + } else if (which == 1) { + String url1 = urlFinal; + if (url1.startsWith("mailto:")) { + url1 = url1.substring(7); + } else if (url1.startsWith("tel:")) { + url1 = url1.substring(4); + } + AndroidUtilities.addToClipboard(url1); + AlertUtil.showToast(LocaleController.getString("LinkCopied", R.string.LinkCopied)); + } + return Unit.INSTANCE; + }); showDialog(builder.create()); } else { openUrl(urlFinal); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java index 6c2c162c9..a6c4c96a9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java @@ -24,6 +24,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.DataSetObserver; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Outline; @@ -173,6 +174,7 @@ import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.RuntimeUtil; import cn.hutool.core.util.StrUtil; import kotlin.Unit; +import kotlin.text.StringsKt; import libv2ray.Libv2ray; import tw.nekomimi.nekogram.BottomBuilder; import tw.nekomimi.nekogram.ExternalGcm; @@ -953,7 +955,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (user == null) { return; } - imageUpdater.openMenu(user.photo != null && user.photo.photo_big != null && !(user.photo instanceof TLRPC.TL_userProfilePhotoEmpty), () -> MessagesController.getInstance(currentAccount).deleteUserPhoto(null), dialog -> {}); + imageUpdater.openMenu(user.photo != null && user.photo.photo_big != null && !(user.photo instanceof TLRPC.TL_userProfilePhotoEmpty), () -> MessagesController.getInstance(currentAccount).deleteUserPhoto(null), dialog -> { + }); } else { openAvatar(); } @@ -1406,6 +1409,20 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. }); } + private void generateLink() { + TLRPC.TL_messages_exportChatInvite req = new TLRPC.TL_messages_exportChatInvite(); + req.peer = getMessagesController().getInputPeer(-chat_id); + final int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + if (error == null) { + TLRPC.TL_chatInviteExported invite = (TLRPC.TL_chatInviteExported) response; + ProxyUtil.showQrDialog(getParentActivity(), invite.link, imageSize -> Bitmap.createScaledBitmap(avatarImage.getImageReceiver().getBitmap(), imageSize, imageSize, true)); + } else { + AlertUtil.showToast(error); + } + })); + getConnectionsManager().bindRequestToGuid(reqId, classGuid); + } + @Override public void onFragmentDestroy() { super.onFragmentDestroy(); @@ -1619,7 +1636,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. String text = null; if (user_id != 0) { TLRPC.User user = getMessagesController().getUser(user_id); - if (user == null) { + if (user == null || StrUtil.isBlank(user.username)) { return; } if (botInfo != null && userInfo != null && !TextUtils.isEmpty(userInfo.about) && id == share) { @@ -1634,8 +1651,15 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } if (chatInfo != null && !TextUtils.isEmpty(chatInfo.about) && id == share) { text = String.format("%s\nhttps://" + getMessagesController().linkPrefix + "/%s", chatInfo.about, chat.username); - } else { + } else if (StrUtil.isNotBlank(chat.username)) { text = String.format("https://" + getMessagesController().linkPrefix + "/%s", chat.username); + } else if (id == qr_code && ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_INVITE)) { + if (chatInfo != null && chatInfo.exported_invite != null) { + text = chatInfo.exported_invite.link; + } else { + generateLink(); + return; + } } } if (TextUtils.isEmpty(text)) { @@ -1647,7 +1671,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. intent.putExtra(Intent.EXTRA_TEXT, text); startActivityForResult(Intent.createChooser(intent, LocaleController.getString("BotShare", R.string.BotShare)), 500); } else { - ProxyUtil.showQrDialog(getParentActivity(), text); + if (BuildVars.DEBUG_PRIVATE_VERSION) { + AlertUtil.showToast(text); + } + ProxyUtil.showQrDialog(getParentActivity(), text, avatarImage.getImageReceiver().getBitmap() == null ? null : imageSize -> Bitmap.createScaledBitmap(avatarImage.getImageReceiver().getBitmap(), imageSize, imageSize, true)); } } catch (Exception e) { FileLog.e(e); @@ -1933,7 +1960,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. videoCallItem = menu.addItem(video_call_item, R.drawable.profile_video); videoCallItem.setContentDescription(LocaleController.getString("VideoCall", R.string.VideoCall)); if (chat_id != 0) { - callItem = menu.addItem(call_item, R.drawable.msg_voicechat2); + callItem = menu.addItem(call_item, R.drawable.baseline_keyboard_voice_24); callItem.setContentDescription(LocaleController.getString("VoipGroupVoiceChat", R.string.VoipGroupVoiceChat)); } else { callItem = menu.addItem(call_item, R.drawable.ic_call); @@ -3162,7 +3189,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (user == null) { return; } - imageUpdater.openMenu(user.photo != null && user.photo.photo_big != null && !(user.photo instanceof TLRPC.TL_userProfilePhotoEmpty), () -> MessagesController.getInstance(currentAccount).deleteUserPhoto(null), dialog -> { }); + imageUpdater.openMenu(user.photo != null && user.photo.photo_big != null && !(user.photo instanceof TLRPC.TL_userProfilePhotoEmpty), () -> MessagesController.getInstance(currentAccount).deleteUserPhoto(null), dialog -> { + }); } else { openAvatar(); } @@ -5875,7 +5903,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. otherItem.addSubItem(invite_to_group, R.drawable.baseline_group_add_24, LocaleController.getString("BotInvite", R.string.BotInvite)); } otherItem.addSubItem(share, R.drawable.baseline_forward_24, LocaleController.getString("BotShare", R.string.BotShare)); - otherItem.addSubItem(qr_code, R.drawable.wallet_qr, LocaleController.getString("ShareQRCode", R.string.ShareQRCode)); } else { otherItem.addSubItem(add_contact, R.drawable.baseline_person_add_24, LocaleController.getString("AddContact", R.string.AddContact)); } @@ -5899,18 +5926,30 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (!UserObject.isDeleted(user) && !isBot && currentEncryptedChat == null && !userBlocked && user_id != 333000 && user_id != 777000 && user_id != 42777) { otherItem.addSubItem(start_secret_chat, R.drawable.msg_start_secret, LocaleController.getString("StartEncryptedChat", R.string.StartEncryptedChat)); } + if (StrUtil.isNotBlank(user.username)) { + otherItem.addSubItem(qr_code, R.drawable.wallet_qr, LocaleController.getString("ShareQRCode", R.string.ShareQRCode)); + } otherItem.addSubItem(add_shortcut, R.drawable.msg_home, LocaleController.getString("AddShortcut", R.string.AddShortcut)); } } else if (chat_id != 0) { TLRPC.Chat chat = getMessagesController().getChat(chat_id); hasVoiceChatItem = false; + if (chat != null && (chat.has_link || (chatInfo != null && chatInfo.linked_chat_id != 0))) { + String text; + if (!chat.megagroup) { + text = LocaleController.getString("LinkedGroupChat", R.string.LinkedGroupChat); + } else { + text = LocaleController.getString("LinkedChannelChat", R.string.LinkedChannelChat); + } + otherItem.addSubItem(view_discussion, R.drawable.baseline_layers_24, text); + } if (ChatObject.isChannel(chat)) { if (ChatObject.hasAdminRights(chat) || chat.megagroup) { editItemVisible = true; } if (chatInfo != null) { if (chat.megagroup && ChatObject.canManageCalls(chat) && chatInfo.call == null) { - otherItem.addSubItem(call_item, R.drawable.msg_voicechat, LocaleController.getString("StartVoipChat", R.string.StartVoipChat)); + otherItem.addSubItem(call_item, R.drawable.baseline_keyboard_voice_24, LocaleController.getString("StartVoipChat", R.string.StartVoipChat)); hasVoiceChatItem = true; } if (chatInfo.can_view_stats) { @@ -5928,22 +5967,18 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else { if (!TextUtils.isEmpty(chat.username)) { otherItem.addSubItem(share, R.drawable.msg_share, LocaleController.getString("BotShare", R.string.BotShare)); - otherItem.addSubItem(qr_code, R.drawable.wallet_qr, LocaleController.getString("ShareQRCode", R.string.ShareQRCode)); - } - if (chatInfo != null && chatInfo.linked_chat_id != 0) { - otherItem.addSubItem(view_discussion, R.drawable.msg_discussion, LocaleController.getString("ViewDiscussion", R.string.ViewDiscussion)); } if (!currentChat.creator && !currentChat.left && !currentChat.kicked) { otherItem.addSubItem(leave_group, R.drawable.baseline_exit_to_app_24, LocaleController.getString("LeaveChannelMenu", R.string.LeaveChannelMenu)); } - if (ChatObject.hasAdminRights(currentChat)) { - otherItem.addSubItem(event_log, R.drawable.group_log, LocaleController.getString("EventLog", R.string.EventLog)); - } + } + if (ChatObject.hasAdminRights(currentChat)) { + otherItem.addSubItem(event_log, R.drawable.baseline_content_paste_24, LocaleController.getString("EventLog", R.string.EventLog)); } } else { if (chatInfo != null) { if (ChatObject.canManageCalls(chat) && chatInfo.call == null) { - otherItem.addSubItem(call_item, R.drawable.msg_voicechat, LocaleController.getString("StartVoipChat", R.string.StartVoipChat)); + otherItem.addSubItem(call_item, R.drawable.baseline_keyboard_voice_24, LocaleController.getString("StartVoipChat", R.string.StartVoipChat)); hasVoiceChatItem = true; } ChatObject.Call call = getMessagesController().getGroupCall(chat_id, false); @@ -5958,6 +5993,9 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } otherItem.addSubItem(leave_group, R.drawable.baseline_exit_to_app_24, LocaleController.getString("DeleteAndExit", R.string.DeleteAndExit)); } + if (StrUtil.isNotBlank(chat.username) || ChatObject.canUserDoAdminAction(chat, ChatObject.ACTION_INVITE)) { + otherItem.addSubItem(qr_code, R.drawable.wallet_qr, LocaleController.getString("ShareQRCode", R.string.ShareQRCode)); + } otherItem.addSubItem(add_shortcut, R.drawable.baseline_home_24, LocaleController.getString("AddShortcut", R.string.AddShortcut)); } @@ -6701,7 +6739,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. aboutLinkCell.setTextAndValue(value, LocaleController.getString("UserBio", R.string.UserBio), false); currentBio = userInfo != null ? userInfo.about : null; } else { - aboutLinkCell.setTextAndValue( LocaleController.getString("UserBioDetail", R.string.UserBioDetail), LocaleController.getString("UserBio", R.string.UserBio), false); + aboutLinkCell.setTextAndValue(LocaleController.getString("UserBioDetail", R.string.UserBioDetail), LocaleController.getString("UserBio", R.string.UserBio), false); currentBio = null; } } @@ -6941,7 +6979,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. position == numberSectionRow || position == helpHeaderRow || position == debugHeaderRow) { return 1; } else if (position == phoneRow || position == usernameRow || position == locationRow || - position == numberRow || position == setUsernameRow ) { + position == numberRow || position == setUsernameRow) { return 2; } else if (position == userInfoRow || position == channelInfoRow || position == bioRow) { return 3; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProxyListActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProxyListActivity.java index 0e8825ff1..33bcd37aa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProxyListActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProxyListActivity.java @@ -850,7 +850,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente LocaleController.getString("ImportProxyFromClipboard", R.string.ImportProxyFromClipboard), LocaleController.getString("ScanQRCode", R.string.ScanQRCode) - }, (i,t,c) -> { + }, null, (i,t,c) -> { if (i == 0) { diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/BottomBuilder.kt b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/BottomBuilder.kt index 38a02affc..28fddee9e 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/BottomBuilder.kt +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/BottomBuilder.kt @@ -308,14 +308,13 @@ class BottomBuilder(val ctx: Context) { } - @JvmOverloads - fun addItems(text: Array, icon: (Int) -> Int = { 0 }, listener: (index: Int, text: String, cell: TextCell) -> Unit): List { + fun addItems(text: Array, icon: Array?, listener: (index: Int, text: String, cell: TextCell) -> Unit): List { val list = mutableListOf() text.forEachIndexed { index, textI -> - list.add(addItem(textI ?: return@forEachIndexed, icon(index)) { cell -> + list.add(addItem(textI ?: return@forEachIndexed, icon?.get(index) ?: 0) { cell -> listener(index, textI, cell) diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/ProxyUtil.kt b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/ProxyUtil.kt index 5cf700521..39b9c877f 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/ProxyUtil.kt +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/ProxyUtil.kt @@ -9,13 +9,18 @@ import android.content.ContextWrapper import android.content.Intent import android.content.pm.PackageManager import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color import android.os.Build import android.os.Environment import android.util.Base64 import android.view.Gravity +import android.view.View +import android.view.WindowManager import android.widget.ImageView import android.widget.LinearLayout import android.widget.Toast +import androidx.core.view.setPadding import com.google.zxing.* import com.google.zxing.common.GlobalHistogramBinarizer import com.google.zxing.qrcode.QRCodeReader @@ -35,6 +40,7 @@ import java.net.NetworkInterface import java.util.* import kotlin.collections.HashMap + object ProxyUtil { @JvmStatic @@ -351,19 +357,26 @@ object ProxyUtil { } @JvmStatic - fun showQrDialog(ctx: Context, text: String): AlertDialog { + @JvmOverloads + fun showQrDialog(ctx: Context, text: String, icon: ((Int) -> Bitmap)? = null): AlertDialog { - val code = createQRCode(text) + val code = createQRCode(text, icon = icon) ctx.setTheme(R.style.Theme_TMessages) return AlertDialog.Builder(ctx).setView(LinearLayout(ctx).apply { + gravity = Gravity.CENTER + setBackgroundColor(Color.TRANSPARENT) + addView(LinearLayout(ctx).apply { + val root = this gravity = Gravity.CENTER + setBackgroundColor(Color.WHITE) + setPadding(AndroidUtilities.dp(16f)) - val width = AndroidUtilities.dp(330f) + val width = AndroidUtilities.dp(260f) addView(ImageView(ctx).apply { @@ -405,12 +418,16 @@ object ProxyUtil { saveTo.outputStream().use { - code?.compress(Bitmap.CompressFormat.JPEG, 100, it); + loadBitmapFromView(root).compress(Bitmap.CompressFormat.JPEG, 100, it); } AndroidUtilities.addMediaToGallery(saveTo.path) + AlertUtil.showToast(LocaleController.getString("PhotoSavedHint", R.string.PhotoSavedHint)) + }.onFailure { + FileLog.e(it) + AlertUtil.showToast(it) } } @@ -423,40 +440,38 @@ object ProxyUtil { }, LinearLayout.LayoutParams(width, width)) - }, LinearLayout.LayoutParams(-1, -1).apply { + }, LinearLayout.LayoutParams(-2, -2).apply { gravity = Gravity.CENTER }) - }).show() + }).create().apply { + + show() + window?.setBackgroundDrawableResource(android.R.color.transparent) + + } } + private fun loadBitmapFromView(v: View): Bitmap { + val b = Bitmap.createBitmap(v.width, v.height, Bitmap.Config.ARGB_8888) + val c = Canvas(b) + v.layout(v.left, v.top, v.right, v.bottom) + v.draw(c) + return b + } + @JvmStatic - fun createQRCode(text: String, size: Int = 800): Bitmap? { - try { + fun createQRCode(text: String, size: Int = 768, icon: ((Int) -> Bitmap)? = null): Bitmap { + return try { val hints = HashMap() - hints[EncodeHintType.CHARACTER_SET] = "utf-8" hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M - //hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.H - val bitMatrix = QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, size, size, hints) - val pixels = IntArray(size * size) - for (y in 0 until size) { - for (x in 0 until size) { - if (bitMatrix.get(x, y)) { - pixels[y * size + x] = 0xff000000.toInt() - } else { - pixels[y * size + x] = 0xffffffff.toInt() - } - } - } - val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) - bitmap.setPixels(pixels, 0, size, 0, 0, size, size) - return bitmap + QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, size, size, hints, null, null, icon) } catch (e: WriterException) { FileLog.e(e); - return Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) + Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) } } @@ -471,24 +486,12 @@ object ProxyUtil { try { - val result = try { - qrReader.decode(BinaryBitmap(GlobalHistogramBinarizer(source)), mapOf( - DecodeHintType.TRY_HARDER to true - )) - } catch (e: NotFoundException) { - qrReader.decode(BinaryBitmap(GlobalHistogramBinarizer(source.invert())), mapOf( - DecodeHintType.TRY_HARDER to true - )) - } + val result = qrReader.decode(BinaryBitmap(GlobalHistogramBinarizer(source)), mapOf( + DecodeHintType.TRY_HARDER to true + )) showLinkAlert(ctx, result.text) - val intArr = arrayListOf().toIntArray() - - } catch (ex: NoSuchMethodError) { - - AlertUtil.showSimpleAlert(ctx, "很抱歉, 這是一個已知的問題, 但您現在無法掃碼, 因爲您正在使用糟糕的Android系統, 直到 Google Zxing 為您的設備做出優化.") - } catch (e: Throwable) { AlertUtil.showToast(LocaleController.getString("NoQrFound", R.string.NoQrFound)) diff --git a/TMessagesProj/src/main/res/drawable/baseline_content_paste_24.xml b/TMessagesProj/src/main/res/drawable/baseline_content_paste_24.xml new file mode 100644 index 000000000..dd214b1e4 --- /dev/null +++ b/TMessagesProj/src/main/res/drawable/baseline_content_paste_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/TMessagesProj/src/main/res/drawable/baseline_keyboard_voice_24.xml b/TMessagesProj/src/main/res/drawable/baseline_keyboard_voice_24.xml new file mode 100644 index 000000000..d28d4165b --- /dev/null +++ b/TMessagesProj/src/main/res/drawable/baseline_keyboard_voice_24.xml @@ -0,0 +1,9 @@ + + +