diff --git a/app/build.gradle b/app/build.gradle index 5f08145..1202c67 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "com.android.NBZxing" minSdkVersion 17 targetSdkVersion 30 - versionCode 21 - versionName "1.30" + versionCode 22 + versionName "1.40" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" signingConfig signingConfigs.release compileOptions { diff --git a/app/src/main/java/com/android/NBZxing/CusScanView.kt b/app/src/main/java/com/android/NBZxing/CusScanView.kt index 78ee066..e68e1ea 100644 --- a/app/src/main/java/com/android/NBZxing/CusScanView.kt +++ b/app/src/main/java/com/android/NBZxing/CusScanView.kt @@ -30,7 +30,7 @@ class CusScanView @JvmOverloads constructor(context: Context, attributeSet: Attr * 4 ScanTypeConfig.TWO_DIMENSION 所有二维码格式 * 5 ScanTypeConfig.ONE_DIMENSION 所有一维码格式 */ - override fun getScanType(): ScanTypeConfig { + override fun configScanType(): ScanTypeConfig { return ScanTypeConfig.HIGH_FREQUENCY } @@ -47,4 +47,12 @@ class CusScanView @JvmOverloads constructor(context: Context, attributeSet: Attr Toast.makeText(context, "未扫描到内容", Toast.LENGTH_SHORT).show() else Toast.makeText(context, content, Toast.LENGTH_SHORT).show() } + + /*** + * 是否支持黑边二维码扫描 + */ + override fun isSupportBlackEdgeQrScan(): Boolean { + return true + } + } \ No newline at end of file diff --git a/app/src/main/java/com/android/NBZxing/CusScanView2.kt b/app/src/main/java/com/android/NBZxing/CusScanView2.kt index 2f21170..2f1ea39 100644 --- a/app/src/main/java/com/android/NBZxing/CusScanView2.kt +++ b/app/src/main/java/com/android/NBZxing/CusScanView2.kt @@ -30,7 +30,7 @@ class CusScanView2 @JvmOverloads constructor(context: Context, attributeSet: Att * 4 ScanTypeConfig.TWO_DIMENSION 所有二维码格式 * 5 ScanTypeConfig.ONE_DIMENSION 所有一维码格式 */ - override fun getScanType(): ScanTypeConfig { + override fun configScanType(): ScanTypeConfig { return ScanTypeConfig.HIGH_FREQUENCY } diff --git a/module_camera/src/main/java/com/ailiwean/core/Config.java b/module_camera/src/main/java/com/ailiwean/core/Config.java index 60fe4a7..bd25898 100644 --- a/module_camera/src/main/java/com/ailiwean/core/Config.java +++ b/module_camera/src/main/java/com/ailiwean/core/Config.java @@ -29,7 +29,7 @@ public class Config { //自动缩放 public static final int AUTO_ZOOM = 2; - //实时位置 + //实时二维码探测点位置 public static final int RT_LOCATION = 3; //############################################### @@ -63,6 +63,9 @@ public static boolean is270() { return displayOrientation == 270; } + /*###############################################*/ + + //灰度算法类路径 public static String GARY_SCALE_PATH = "com.ailiwean.module_grayscale.GrayScaleDispatch"; @@ -74,4 +77,10 @@ public static boolean hasDepencidesScale() { } return true; } + + /*###############################################*/ + + //是否支持黑边二维码识别 + public static boolean isSupportBlackEdge = true; + } diff --git a/module_camera/src/main/java/com/ailiwean/core/able/AbleManager.kt b/module_camera/src/main/java/com/ailiwean/core/able/AbleManager.kt index 791468f..8ea6196 100644 --- a/module_camera/src/main/java/com/ailiwean/core/able/AbleManager.kt +++ b/module_camera/src/main/java/com/ailiwean/core/able/AbleManager.kt @@ -43,9 +43,9 @@ class AbleManager private constructor(handler: Handler) : PixsValuesAble(handler fun loadAbility() { ableList.clear() -// ableList.add(XQRScanCrudeAble(handlerHolder.get())) + ableList.add(XQRScanCrudeAble(handlerHolder.get())) ableList.add(XQRScanZoomAble(handlerHolder.get())) -// ableList.add(XQRScanAbleRotate(handlerHolder.get())) + ableList.add(XQRScanAbleRotate(handlerHolder.get())) ableList.add(LighSolveAble(handlerHolder.get())) // ableList.add(XQRScanAble(handlerHolder.get())) // ableList.add(GrayscaleStrengAble(handlerHolder.get())) diff --git a/module_camera/src/main/java/com/ailiwean/core/able/XQRScanAble.java b/module_camera/src/main/java/com/ailiwean/core/able/XQRScanAble.java index edc347e..ec6991b 100644 --- a/module_camera/src/main/java/com/ailiwean/core/able/XQRScanAble.java +++ b/module_camera/src/main/java/com/ailiwean/core/able/XQRScanAble.java @@ -29,7 +29,7 @@ protected void needParseDeploy(PlanarYUVLuminanceSource source, boolean isNative if (result != null && result.getText() != null) return; result = toLaunchParse(source.getHybridBinary()); - if (result != null && !"".equals(result.getText()) && result.getText() != null) { + if (result != null && result.getText() != null && !"".equals(result.getText())) { sendMessage(Config.RT_LOCATION, ScanHelper.rotatePoint(result.getResultPoints())); sendMessage(Config.SCAN_RESULT, covertResult(result)); diff --git a/module_camera/src/main/java/com/ailiwean/core/able/XQRScanAbleRotate.java b/module_camera/src/main/java/com/ailiwean/core/able/XQRScanAbleRotate.java index 219c557..eabfcf1 100644 --- a/module_camera/src/main/java/com/ailiwean/core/able/XQRScanAbleRotate.java +++ b/module_camera/src/main/java/com/ailiwean/core/able/XQRScanAbleRotate.java @@ -27,7 +27,7 @@ public class XQRScanAbleRotate extends PixsValuesAble { @Override protected void needParseDeploy(PlanarYUVLuminanceSource source, boolean isNative) { result = toLaunchParse(new HybridBinarizer(source.onlyCopyWarpRotate())); - if (result != null && !"".equals(result.getText()) && result.getText() != null) { + if (result != null && result.getText() != null && !"".equals(result.getText())) { sendMessage(Config.SCAN_RESULT, covertResultRotate(result)); } } diff --git a/module_camera/src/main/java/com/ailiwean/core/able/XQRScanCrudeAble.java b/module_camera/src/main/java/com/ailiwean/core/able/XQRScanCrudeAble.java index 37b34e6..952c1fc 100644 --- a/module_camera/src/main/java/com/ailiwean/core/able/XQRScanCrudeAble.java +++ b/module_camera/src/main/java/com/ailiwean/core/able/XQRScanCrudeAble.java @@ -29,7 +29,7 @@ protected void needParseDeploy(PlanarYUVLuminanceSource source, boolean isNative if (result != null && result.getText() != null) return; result = toLaunchParse(source.getHybridBinaryCurde()); - if (result != null && !"".equals(result.getText()) && result.getText() != null) { + if (result != null && result.getText() != null && !"".equals(result.getText())) { sendMessage(Config.SCAN_RESULT, covertResult(result)); } } diff --git a/module_camera/src/main/java/com/ailiwean/core/able/XQRScanZoomAble.java b/module_camera/src/main/java/com/ailiwean/core/able/XQRScanZoomAble.java index 118ab48..846056a 100644 --- a/module_camera/src/main/java/com/ailiwean/core/able/XQRScanZoomAble.java +++ b/module_camera/src/main/java/com/ailiwean/core/able/XQRScanZoomAble.java @@ -50,10 +50,8 @@ protected void needParseDeploy(PlanarYUVLuminanceSource source, boolean isNative } else if (lenght < Config.scanRect.getPreX() / 3 * 2) { Config.currentZoom += 0.07; } - zoomTime = System.currentTimeMillis(); lastLenght = lenght; - - // sendMessage(Config.AUTO_ZOOM, Config.currentZoom + ""); + sendMessage(Config.AUTO_ZOOM, Config.currentZoom + ""); } } diff --git a/module_camera/src/main/java/com/ailiwean/core/view/FreeZxingView.kt b/module_camera/src/main/java/com/ailiwean/core/view/FreeZxingView.kt index de3c275..4e1e221 100644 --- a/module_camera/src/main/java/com/ailiwean/core/view/FreeZxingView.kt +++ b/module_camera/src/main/java/com/ailiwean/core/view/FreeZxingView.kt @@ -2,16 +2,14 @@ package com.ailiwean.core.view import android.content.Context -import android.graphics.* +import android.graphics.Bitmap import android.os.Handler import android.os.HandlerThread import android.os.Looper import android.os.Message import android.util.AttributeSet -import android.util.Log import android.view.LayoutInflater import android.view.View -import android.widget.Toast import com.ailiwean.core.Config.* import com.ailiwean.core.Result import com.ailiwean.core.able.AbleManager @@ -43,6 +41,7 @@ abstract class FreeZxingView @JvmOverloads constructor(context: Context, attribu //配置扫码类型 initScanType() + } /*** @@ -107,10 +106,11 @@ abstract class FreeZxingView @JvmOverloads constructor(context: Context, attribu setZoom(message.obj.toString().toFloat()) } - //实时位置 + //实时探测点位置 RT_LOCATION -> { - poinF = message.obj as Array - invalidate() + //数组长度为3 +// poinF = message.obj as Array +// invalidate() } } } @@ -118,23 +118,25 @@ abstract class FreeZxingView @JvmOverloads constructor(context: Context, attribu return true } - var poinF: Array? = null - - val paint by lazy { - val paint = Paint() - paint.color = Color.RED - paint.style = Paint.Style.FILL - paint - } - - - override fun draw(canvas: Canvas?) { - super.draw(canvas) - if (poinF != null) - poinF?.forEach { - canvas?.drawCircle(it.x, it.y, 5f, paint) - } - } +// var poinF: Array? = null + +// val paint by lazy { +// val paint = Paint() +// paint.color = Color.RED +// paint.textSize = 15f +// paint.style = Paint.Style.FILL +// paint +// } +// +// +// override fun draw(canvas: Canvas?) { +// super.draw(canvas) +// if (poinF != null) +// poinF?.forEachIndexed { index, it -> +//// canvas?.drawText("$index", pointF.x, pointF.y, paint) +// canvas?.drawCircle(it.x, it.y, 5f, paint) +// } +// } /*** * 相机采集数据实时回调 @@ -269,7 +271,8 @@ abstract class FreeZxingView @JvmOverloads constructor(context: Context, attribu * 配置扫码类型 */ private fun initScanType() { - scanTypeConfig = getScanType() + scanTypeConfig = configScanType() + isSupportBlackEdge = isSupportBlackEdgeQrScan() } /*** @@ -317,10 +320,18 @@ abstract class FreeZxingView @JvmOverloads constructor(context: Context, attribu /*** * 提供扫码类型 */ - open fun getScanType(): ScanTypeConfig { + open fun configScanType(): ScanTypeConfig { return ScanTypeConfig.HIGH_FREQUENCY } + /*** + * 是否支持黑边二维码识别-会导致缩放变得灵敏 + * 默认支持 + */ + open fun isSupportBlackEdgeQrScan(): Boolean { + return true + } + /*** * 业务Handler */ diff --git a/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/QRCodeReader.java b/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/QRCodeReader.java index d711e97..69f451f 100644 --- a/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/QRCodeReader.java +++ b/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/QRCodeReader.java @@ -16,6 +16,7 @@ package com.ailiwean.core.zxing.core.qrcode; +import com.ailiwean.core.Config; import com.ailiwean.core.zxing.core.BarcodeFormat; import com.ailiwean.core.zxing.core.BinaryBitmap; import com.ailiwean.core.zxing.core.ChecksumException; @@ -32,6 +33,7 @@ import com.ailiwean.core.zxing.core.qrcode.decoder.Decoder; import com.ailiwean.core.zxing.core.qrcode.decoder.QRCodeDecoderMetaData; import com.ailiwean.core.zxing.core.qrcode.detector.Detector; +import com.ailiwean.core.zxing.core.qrcode.detector.Detector2; import java.util.List; import java.util.Map; @@ -83,7 +85,14 @@ public final Result decode(BinaryBitmap image, Map hints) try { detectorResult = new Detector(image.getBlackMatrix()).detect(hints); } catch (NotFoundException e) { - return null; + if (Config.isSupportBlackEdge) { + try { + detectorResult = new Detector2(image.getBlackMatrix()).detect(hints); + } catch (NotFoundException notFoundException) { + return null; + } + } else + return null; } points = detectorResult.getPoints(); try { @@ -207,7 +216,8 @@ private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundExcepti return bits; } - private static float moduleSize(int[] leftTopBlack, BitMatrix image) throws NotFoundException { + private static float moduleSize(int[] leftTopBlack, BitMatrix image) throws + NotFoundException { int height = image.getHeight(); int width = image.getWidth(); int x = leftTopBlack[0]; diff --git a/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/decoder/BitMatrixParser.java b/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/decoder/BitMatrixParser.java index 5548754..fa01fec 100644 --- a/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/decoder/BitMatrixParser.java +++ b/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/decoder/BitMatrixParser.java @@ -25,222 +25,224 @@ */ 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; + 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; } - // 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); - } + /** + *

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 { - // 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); - } + if (parsedFormatInfo != null) { + return parsedFormatInfo; + } - 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; - } + // 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); + } - int dimension = bitMatrix.getHeight(); + // 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); + } - int provisionalVersion = (dimension - 17) / 4; - if (provisionalVersion <= 6) { - return Version.getVersionForNumber(provisionalVersion); + parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits1, formatInfoBits2); + if (parsedFormatInfo != null) { + return parsedFormatInfo; + } + throw FormatException.getFormatInstance(); } - // 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); - } - } + /** + *

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 { - Version theParsedVersion = Version.decodeVersionInformation(versionBits); - if (theParsedVersion != null && theParsedVersion.getDimensionForVersion() == dimension) { - parsedVersion = theParsedVersion; - return theParsedVersion; - } + if (parsedVersion != null) { + return parsedVersion; + } - // 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); - } - } + int dimension = bitMatrix.getHeight(); - 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; + 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); } - // If we've made a whole byte, save it off - if (bitsRead == 8) { - result[resultOffset++] = (byte) currentByte; - bitsRead = 0; - currentByte = 0; + } + + 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); } - } } - } - 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 + + 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; } - 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); - } - } + + /** + * 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/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/detector/Detector.java b/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/detector/Detector.java index e86cf41..4fb932d 100644 --- a/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/detector/Detector.java +++ b/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/detector/Detector.java @@ -16,8 +16,6 @@ package com.ailiwean.core.zxing.core.qrcode.detector; -import android.util.Log; - import com.ailiwean.core.zxing.core.DecodeHintType; import com.ailiwean.core.zxing.core.FormatException; import com.ailiwean.core.zxing.core.NotFoundException; @@ -79,7 +77,7 @@ public final DetectorResult detect(Map hints) throws NotFound resultPointCallback = hints == null ? null : (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); - FinderPatternFinder2 finder = new FinderPatternFinder2(image, resultPointCallback); + FinderPatternFinder finder = new FinderPatternFinder(image, resultPointCallback); FinderPatternInfo info = finder.find(hints); return processFinderPatternInfo(info); @@ -113,7 +111,6 @@ protected final DetectorResult processFinderPatternInfo(FinderPatternInfo info) 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 { diff --git a/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/detector/Detector2.java b/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/detector/Detector2.java new file mode 100644 index 0000000..65c9663 --- /dev/null +++ b/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/detector/Detector2.java @@ -0,0 +1,404 @@ +/* + * 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.ailiwean.core.zxing.core.qrcode.detector; + +import com.ailiwean.core.zxing.core.DecodeHintType; +import com.ailiwean.core.zxing.core.FormatException; +import com.ailiwean.core.zxing.core.NotFoundException; +import com.ailiwean.core.zxing.core.ResultPoint; +import com.ailiwean.core.zxing.core.ResultPointCallback; +import com.ailiwean.core.zxing.core.common.BitMatrix; +import com.ailiwean.core.zxing.core.common.DetectorResult; +import com.ailiwean.core.zxing.core.common.GridSampler; +import com.ailiwean.core.zxing.core.common.PerspectiveTransform; +import com.ailiwean.core.zxing.core.common.detector.MathUtils; +import com.ailiwean.core.zxing.core.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 Detector2 { + + private final BitMatrix image; + private ResultPointCallback resultPointCallback; + + public Detector2(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); + + FinderPatternFinder2 finder = new FinderPatternFinder2(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 scaleRatio = (float) (Math.random() * 0.6f + 0.3f); + float moduleSize = calculateModuleSize(topLeft, topRight, bottomLeft) * scaleRatio; + 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: + throw NotFoundException.getNotFoundInstance(); + } + return dimension; + } + + /** + *

Computes an average estimated module size based on estimated derived from the positions + * of the three finder patterns.

+ * + * @param topLeft detected top-left finder pattern center + * @param topRight detected top-right finder pattern center + * @param bottomLeft detected bottom-left finder pattern center + * @return estimated module size + */ + protected final float calculateModuleSize(ResultPoint topLeft, + ResultPoint topRight, + ResultPoint bottomLeft) { + // Take the average + return (calculateModuleSizeOneWay(topLeft, topRight) + + calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f; + } + + /** + *

Estimates module size based on two finder patterns -- it uses + * {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the + * width of each, measuring along the axis between their centers.

+ */ + private float calculateModuleSizeOneWay(ResultPoint pattern, ResultPoint otherPattern) { + float moduleSizeEst1 = sizeOfBlackWhiteBlackRunBothWays((int) pattern.getX(), + (int) pattern.getY(), + (int) otherPattern.getX(), + (int) otherPattern.getY()); + float moduleSizeEst2 = sizeOfBlackWhiteBlackRunBothWays((int) otherPattern.getX(), + (int) otherPattern.getY(), + (int) pattern.getX(), + (int) pattern.getY()); + if (Float.isNaN(moduleSizeEst1)) { + return moduleSizeEst2 / 7.0f; + } + if (Float.isNaN(moduleSizeEst2)) { + return moduleSizeEst1 / 7.0f; + } + // Average them, and divide by 7 since we've counted the width of 3 black modules, + // and 1 white and 1 black module on either side. Ergo, divide sum by 14. + return (moduleSizeEst1 + moduleSizeEst2) / 14.0f; + } + + /** + * See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of + * a finder pattern by looking for a black-white-black run from the center in the direction + * of another point (another finder pattern center), and in the opposite direction too. + */ + private float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY) { + + float result = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY); + + // Now count other way -- don't run off image though of course + float scale = 1.0f; + int otherToX = fromX - (toX - fromX); + if (otherToX < 0) { + scale = fromX / (float) (fromX - otherToX); + otherToX = 0; + } else if (otherToX >= image.getWidth()) { + scale = (image.getWidth() - 1 - fromX) / (float) (otherToX - fromX); + otherToX = image.getWidth() - 1; + } + int otherToY = (int) (fromY - (toY - fromY) * scale); + + scale = 1.0f; + if (otherToY < 0) { + scale = fromY / (float) (fromY - otherToY); + otherToY = 0; + } else if (otherToY >= image.getHeight()) { + scale = (image.getHeight() - 1 - fromY) / (float) (otherToY - fromY); + otherToY = image.getHeight() - 1; + } + otherToX = (int) (fromX + (otherToX - fromX) * scale); + + result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY); + + // Middle pixel is double-counted this way; subtract 1 + return result - 1.0f; + } + + /** + *

This method traces a line from a point in the image, in the direction towards another point. + * It begins in a black region, and keeps going until it finds white, then black, then white again. + * It reports the distance from the start to this point.

+ * + *

This is used when figuring out how wide a finder pattern is, when the finder pattern + * may be skewed or rotated.

+ */ + private float sizeOfBlackWhiteBlackRun(int fromX, int fromY, int toX, int toY) { + // Mild variant of Bresenham's algorithm; + // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm + boolean steep = Math.abs(toY - fromY) > Math.abs(toX - fromX); + if (steep) { + int temp = fromX; + fromX = fromY; + fromY = temp; + temp = toX; + toX = toY; + toY = temp; + } + + int dx = Math.abs(toX - fromX); + int dy = Math.abs(toY - fromY); + int error = -dx / 2; + int xstep = fromX < toX ? 1 : -1; + int ystep = fromY < toY ? 1 : -1; + + // In black pixels, looking for white, first or second time. + int state = 0; + // Loop up until x == toX, but not beyond + int xLimit = toX + xstep; + for (int x = fromX, y = fromY; x != xLimit; x += xstep) { + int realX = steep ? y : x; + int realY = steep ? x : y; + + // Does current pixel mean we have moved white to black or vice versa? + // Scanning black in state 0,2 and white in state 1, so if we find the wrong + // color, advance to next state or end if we are in state 2 already + if ((state == 1) == image.get(realX, realY)) { + if (state == 2) { + return MathUtils.distance(x, y, fromX, fromY); + } + state++; + } + + error += dy; + if (error > 0) { + if (y == toY) { + break; + } + y += ystep; + error -= dx; + } + } + // Found black-white-black; give the benefit of the doubt that the next pixel outside the image + // is "white" so this last point at (toX+xStep,toY) is the right ending. This is really a + // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this. + if (state == 2) { + return MathUtils.distance(toX + xstep, toY, fromX, fromY); + } + // else we didn't find even black-white-black; no estimate is really possible + return Float.NaN; + } + + /** + *

Attempts to locate an alignment pattern in a limited region of the image, which is + * guessed to contain it. This method uses {@link AlignmentPattern}.

+ * + * @param overallEstModuleSize estimated module size so far + * @param estAlignmentX x coordinate of center of area probably containing alignment pattern + * @param estAlignmentY y coordinate of above + * @param allowanceFactor number of pixels in all directions to search from the center + * @return {@link AlignmentPattern} if found, or null otherwise + * @throws NotFoundException if an unexpected error occurs during detection + */ + protected final AlignmentPattern findAlignmentInRegion(float overallEstModuleSize, + int estAlignmentX, + int estAlignmentY, + float allowanceFactor) + throws NotFoundException { + // Look for an alignment pattern (3 modules in size) around where it + // should be + int allowance = (int) (allowanceFactor * overallEstModuleSize); + int alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance); + int alignmentAreaRightX = Math.min(image.getWidth() - 1, estAlignmentX + allowance); + if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) { + throw NotFoundException.getNotFoundInstance(); + } + + int alignmentAreaTopY = Math.max(0, estAlignmentY - allowance); + int alignmentAreaBottomY = Math.min(image.getHeight() - 1, estAlignmentY + allowance); + if (alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize * 3) { + throw NotFoundException.getNotFoundInstance(); + } + + AlignmentPatternFinder alignmentFinder = + new AlignmentPatternFinder( + image, + alignmentAreaLeftX, + alignmentAreaTopY, + alignmentAreaRightX - alignmentAreaLeftX, + alignmentAreaBottomY - alignmentAreaTopY, + overallEstModuleSize, + resultPointCallback); + return alignmentFinder.find(); + } + +} diff --git a/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/detector/FinderPatternFinder2.java b/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/detector/FinderPatternFinder2.java index 7851965..a549b55 100644 --- a/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/detector/FinderPatternFinder2.java +++ b/module_camera/src/main/java/com/ailiwean/core/zxing/core/qrcode/detector/FinderPatternFinder2.java @@ -33,6 +33,7 @@ class FinderPatternFinder2 { private final List possibleCenters; private boolean hasSkipped; private final int[] crossCheckStateCount; + private final int[] clearStateCount; private final ResultPointCallback resultPointCallback; /** @@ -48,6 +49,7 @@ public FinderPatternFinder2(BitMatrix image, ResultPointCallback resultPointCall this.image = image; this.possibleCenters = new ArrayList<>(); this.crossCheckStateCount = new int[5]; + clearStateCount = new int[5]; this.resultPointCallback = resultPointCallback; } @@ -133,7 +135,6 @@ final FinderPatternInfo find(Map hints) throws NotFoundExcept } } } - if (foundPatternCross(clearBlackEdgeCount(stateCount))) { boolean confirmed = handlePossibleCenter(clearBlackEdgeCount(stateCount), i, maxJ - getBlackEdgeOffset(stateCount)); if (confirmed) { @@ -145,7 +146,6 @@ final FinderPatternInfo find(Map hints) throws NotFoundExcept } } } - FinderPattern[] patternInfo = selectBestPatterns(); ResultPoint.orderBestPatterns(patternInfo); return new FinderPatternInfo(patternInfo); @@ -157,19 +157,27 @@ final FinderPatternInfo find(Map hints) throws NotFoundExcept * @return */ private int[] clearBlackEdgeCount(int[] statusCounts) { - int[] clone = statusCounts.clone(); - clone[0] = clone[4] = Math.min(clone[0], clone[4]); - return clone; + doClearCopy(statusCounts); + clearStateCount[0] = clearStateCount[4] = (int) (Math.min(clearStateCount[0], clearStateCount[4])); + return clearStateCount; } + private void doClearCopy(int[] stateCount) { + System.arraycopy(stateCount, 0, clearStateCount, 0, stateCount.length); + } + + /*** * 尾部黑边要向前的偏移量 * @param statusCounts * @return */ private int getBlackEdgeOffset(int[] statusCounts) { - if (statusCounts[4] > statusCounts[0]) - return statusCounts[4] - statusCounts[0]; + if (statusCounts[0] / (float) statusCounts[4] > 2f || + statusCounts[0] / (float) statusCounts[4] < 0.5f) { + if (statusCounts[4] > statusCounts[0]) + return statusCounts[4] - statusCounts[0]; + } return 0; } @@ -194,14 +202,10 @@ protected static boolean foundPatternCross(int[] stateCount) { } totalModuleSize += count; } - if (totalModuleSize < 7) { + if (totalModuleSize < 14) { return false; } - if (stateCount[1] == 0 || stateCount[2] == 0 - || stateCount[3] == 0 || stateCount[4] == 0) - return false; - float moduleSize = totalModuleSize / 7.0f; float maxVariance = moduleSize / 2.0f; // Allow less than 50% variance from 1-1-3-1-1 proportions @@ -227,7 +231,7 @@ protected static boolean foundPatternDiagonal(int[] stateCount) { } totalModuleSize += count; } - if (totalModuleSize < 7) { + if (totalModuleSize < 14) { return false; } float moduleSize = totalModuleSize / 7.0f; @@ -557,7 +561,6 @@ protected final boolean handlePossibleCenter(int[] stateCount, int i, int j, 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)) { @@ -745,7 +748,6 @@ private FinderPattern[] selectBestPatterns() throws NotFoundException { } } } - if (distortion == Double.MAX_VALUE) { throw NotFoundException.getNotFoundInstance(); }