From 4edb98bf8a5db5598009869076970630c4f14a65 Mon Sep 17 00:00:00 2001 From: Lee Nony Date: Fri, 6 May 2022 02:00:27 +0800 Subject: [PATCH] =?UTF-8?q?init=20-=20=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 + modules/wechat_qrcode/CMakeLists.txt | 30 + modules/wechat_qrcode/LICENSE | 253 +++ modules/wechat_qrcode/README.md | 12 + .../include/opencv2/wechat_qrcode.hpp | 62 + modules/wechat_qrcode/samples/qrcode.py | 53 + .../wechat_qrcode/samples/qrcode_example.cpp | 70 + modules/wechat_qrcode/src/binarizermgr.cpp | 70 + modules/wechat_qrcode/src/binarizermgr.hpp | 51 + modules/wechat_qrcode/src/decodermgr.cpp | 103 ++ modules/wechat_qrcode/src/decodermgr.hpp | 46 + modules/wechat_qrcode/src/detector/align.cpp | 66 + modules/wechat_qrcode/src/detector/align.hpp | 41 + .../src/detector/ssd_detector.cpp | 57 + .../src/detector/ssd_detector.hpp | 31 + modules/wechat_qrcode/src/imgsource.cpp | 188 ++ modules/wechat_qrcode/src/imgsource.hpp | 63 + modules/wechat_qrcode/src/precomp.hpp | 29 + .../wechat_qrcode/src/scale/super_scale.cpp | 63 + .../wechat_qrcode/src/scale/super_scale.hpp | 32 + modules/wechat_qrcode/src/wechat_qrcode.cpp | 221 +++ modules/wechat_qrcode/src/zxing/binarizer.cpp | 88 + modules/wechat_qrcode/src/zxing/binarizer.hpp | 88 + .../wechat_qrcode/src/zxing/binarybitmap.cpp | 66 + .../wechat_qrcode/src/zxing/binarybitmap.hpp | 53 + .../wechat_qrcode/src/zxing/common/array.hpp | 113 ++ .../adaptive_threshold_mean_binarizer.cpp | 99 ++ .../adaptive_threshold_mean_binarizer.hpp | 38 + .../binarizer/fast_window_binarizer.cpp | 285 ++++ .../binarizer/fast_window_binarizer.hpp | 56 + .../binarizer/global_histogram_binarizer.cpp | 308 ++++ .../binarizer/global_histogram_binarizer.hpp | 55 + .../common/binarizer/hybrid_binarizer.cpp | 424 +++++ .../common/binarizer/hybrid_binarizer.hpp | 83 + .../binarizer/simple_adaptive_binarizer.cpp | 155 ++ .../binarizer/simple_adaptive_binarizer.hpp | 40 + .../src/zxing/common/bitarray.cpp | 239 +++ .../src/zxing/common/bitarray.hpp | 88 + .../src/zxing/common/bitmatrix.cpp | 397 +++++ .../src/zxing/common/bitmatrix.hpp | 115 ++ .../src/zxing/common/bitsource.cpp | 62 + .../src/zxing/common/bitsource.hpp | 57 + .../src/zxing/common/bytematrix.cpp | 50 + .../src/zxing/common/bytematrix.hpp | 58 + .../src/zxing/common/characterseteci.cpp | 111 ++ .../src/zxing/common/characterseteci.hpp | 46 + .../src/zxing/common/counted.hpp | 110 ++ .../src/zxing/common/decoder_result.cpp | 65 + .../src/zxing/common/decoder_result.hpp | 77 + .../src/zxing/common/detector_result.cpp | 27 + .../src/zxing/common/detector_result.hpp | 42 + .../common/greyscale_luminance_source.cpp | 77 + .../common/greyscale_luminance_source.hpp | 44 + .../greyscale_rotated_luminance_source.cpp | 67 + .../greyscale_rotated_luminance_source.hpp | 39 + .../src/zxing/common/grid_sampler.cpp | 119 ++ .../src/zxing/common/grid_sampler.hpp | 34 + .../src/zxing/common/imagecut.cpp | 66 + .../src/zxing/common/imagecut.hpp | 32 + .../wechat_qrcode/src/zxing/common/kmeans.cpp | 89 + .../wechat_qrcode/src/zxing/common/kmeans.hpp | 26 + .../src/zxing/common/mathutils.hpp | 95 ++ .../zxing/common/perspective_transform.cpp | 120 ++ .../zxing/common/perspective_transform.hpp | 39 + .../zxing/common/reedsolomon/genericgf.cpp | 99 ++ .../zxing/common/reedsolomon/genericgf.hpp | 58 + .../common/reedsolomon/genericgfpoly.cpp | 231 +++ .../common/reedsolomon/genericgfpoly.hpp | 43 + .../reedsolomon/reed_solomon_decoder.cpp | 185 ++ .../reedsolomon/reed_solomon_decoder.hpp | 43 + .../wechat_qrcode/src/zxing/common/str.cpp | 96 ++ .../wechat_qrcode/src/zxing/common/str.hpp | 58 + .../src/zxing/common/stringutils.cpp | 683 ++++++++ .../src/zxing/common/stringutils.hpp | 65 + .../src/zxing/common/unicomblock.cpp | 127 ++ .../src/zxing/common/unicomblock.hpp | 47 + .../wechat_qrcode/src/zxing/decodehints.hpp | 30 + .../wechat_qrcode/src/zxing/errorhandler.cpp | 49 + .../wechat_qrcode/src/zxing/errorhandler.hpp | 89 + .../src/zxing/luminance_source.cpp | 59 + .../src/zxing/luminance_source.hpp | 57 + .../zxing/qrcode/decoder/bitmatrixparser.cpp | 240 +++ .../zxing/qrcode/decoder/bitmatrixparser.hpp | 53 + .../src/zxing/qrcode/decoder/datablock.cpp | 103 ++ .../src/zxing/qrcode/decoder/datablock.hpp | 43 + .../src/zxing/qrcode/decoder/datamask.cpp | 120 ++ .../src/zxing/qrcode/decoder/datamask.hpp | 37 + .../decoder/decoded_bit_stream_parser.cpp | 490 ++++++ .../decoder/decoded_bit_stream_parser.hpp | 66 + .../src/zxing/qrcode/decoder/decoder.cpp | 223 +++ .../src/zxing/qrcode/decoder/decoder.hpp | 65 + .../src/zxing/qrcode/decoder/mode.cpp | 91 + .../src/zxing/qrcode/decoder/mode.hpp | 52 + .../decoder/qrcode_decoder_metadata.hpp | 64 + .../qrcode/detector/alignment_pattern.cpp | 42 + .../qrcode/detector/alignment_pattern.hpp | 34 + .../detector/alignment_pattern_finder.cpp | 231 +++ .../detector/alignment_pattern_finder.hpp | 60 + .../src/zxing/qrcode/detector/detector.cpp | 1065 ++++++++++++ .../src/zxing/qrcode/detector/detector.hpp | 145 ++ .../zxing/qrcode/detector/finder_pattern.cpp | 91 + .../zxing/qrcode/detector/finder_pattern.hpp | 59 + .../qrcode/detector/finder_pattern_finder.cpp | 1508 +++++++++++++++++ .../qrcode/detector/finder_pattern_finder.hpp | 136 ++ .../qrcode/detector/finder_pattern_info.cpp | 96 ++ .../qrcode/detector/finder_pattern_info.hpp | 46 + .../zxing/qrcode/detector/pattern_result.cpp | 28 + .../zxing/qrcode/detector/pattern_result.hpp | 48 + .../zxing/qrcode/error_correction_level.cpp | 44 + .../zxing/qrcode/error_correction_level.hpp | 44 + .../src/zxing/qrcode/format_information.cpp | 114 ++ .../src/zxing/qrcode/format_information.hpp | 48 + .../src/zxing/qrcode/qrcode_reader.cpp | 491 ++++++ .../src/zxing/qrcode/qrcode_reader.hpp | 131 ++ .../src/zxing/qrcode/version.cpp | 500 ++++++ .../src/zxing/qrcode/version.hpp | 86 + modules/wechat_qrcode/src/zxing/reader.cpp | 28 + modules/wechat_qrcode/src/zxing/reader.hpp | 39 + modules/wechat_qrcode/src/zxing/result.cpp | 71 + modules/wechat_qrcode/src/zxing/result.hpp | 78 + .../wechat_qrcode/src/zxing/resultpoint.cpp | 101 ++ .../wechat_qrcode/src/zxing/resultpoint.hpp | 47 + modules/wechat_qrcode/src/zxing/zxing.hpp | 78 + modules/wechat_qrcode/test/test_main.cpp | 14 + modules/wechat_qrcode/test/test_precomp.hpp | 14 + modules/wechat_qrcode/test/test_qrcode.cpp | 293 ++++ 126 files changed, 15370 insertions(+) create mode 100644 README.md create mode 100644 modules/wechat_qrcode/CMakeLists.txt create mode 100644 modules/wechat_qrcode/LICENSE create mode 100644 modules/wechat_qrcode/README.md create mode 100644 modules/wechat_qrcode/include/opencv2/wechat_qrcode.hpp create mode 100644 modules/wechat_qrcode/samples/qrcode.py create mode 100644 modules/wechat_qrcode/samples/qrcode_example.cpp create mode 100644 modules/wechat_qrcode/src/binarizermgr.cpp create mode 100644 modules/wechat_qrcode/src/binarizermgr.hpp create mode 100644 modules/wechat_qrcode/src/decodermgr.cpp create mode 100644 modules/wechat_qrcode/src/decodermgr.hpp create mode 100644 modules/wechat_qrcode/src/detector/align.cpp create mode 100644 modules/wechat_qrcode/src/detector/align.hpp create mode 100644 modules/wechat_qrcode/src/detector/ssd_detector.cpp create mode 100644 modules/wechat_qrcode/src/detector/ssd_detector.hpp create mode 100644 modules/wechat_qrcode/src/imgsource.cpp create mode 100644 modules/wechat_qrcode/src/imgsource.hpp create mode 100644 modules/wechat_qrcode/src/precomp.hpp create mode 100644 modules/wechat_qrcode/src/scale/super_scale.cpp create mode 100644 modules/wechat_qrcode/src/scale/super_scale.hpp create mode 100644 modules/wechat_qrcode/src/wechat_qrcode.cpp create mode 100644 modules/wechat_qrcode/src/zxing/binarizer.cpp create mode 100644 modules/wechat_qrcode/src/zxing/binarizer.hpp create mode 100644 modules/wechat_qrcode/src/zxing/binarybitmap.cpp create mode 100644 modules/wechat_qrcode/src/zxing/binarybitmap.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/array.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/binarizer/adaptive_threshold_mean_binarizer.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/binarizer/adaptive_threshold_mean_binarizer.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/binarizer/fast_window_binarizer.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/binarizer/fast_window_binarizer.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/binarizer/global_histogram_binarizer.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/binarizer/global_histogram_binarizer.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/binarizer/hybrid_binarizer.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/binarizer/hybrid_binarizer.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/binarizer/simple_adaptive_binarizer.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/binarizer/simple_adaptive_binarizer.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/bitarray.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/bitarray.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/bitmatrix.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/bitmatrix.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/bitsource.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/bitsource.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/bytematrix.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/bytematrix.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/characterseteci.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/characterseteci.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/counted.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/decoder_result.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/decoder_result.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/detector_result.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/detector_result.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/greyscale_luminance_source.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/greyscale_luminance_source.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/greyscale_rotated_luminance_source.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/greyscale_rotated_luminance_source.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/grid_sampler.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/grid_sampler.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/imagecut.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/imagecut.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/kmeans.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/kmeans.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/mathutils.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/perspective_transform.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/perspective_transform.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgf.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgf.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgfpoly.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgfpoly.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/reedsolomon/reed_solomon_decoder.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/reedsolomon/reed_solomon_decoder.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/str.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/str.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/stringutils.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/stringutils.hpp create mode 100644 modules/wechat_qrcode/src/zxing/common/unicomblock.cpp create mode 100644 modules/wechat_qrcode/src/zxing/common/unicomblock.hpp create mode 100644 modules/wechat_qrcode/src/zxing/decodehints.hpp create mode 100644 modules/wechat_qrcode/src/zxing/errorhandler.cpp create mode 100644 modules/wechat_qrcode/src/zxing/errorhandler.hpp create mode 100644 modules/wechat_qrcode/src/zxing/luminance_source.cpp create mode 100644 modules/wechat_qrcode/src/zxing/luminance_source.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/bitmatrixparser.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/bitmatrixparser.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/datablock.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/datablock.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/datamask.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/datamask.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/decoded_bit_stream_parser.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/decoded_bit_stream_parser.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/decoder.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/decoder.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/mode.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/mode.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/decoder/qrcode_decoder_metadata.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern_finder.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern_finder.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/detector.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/detector.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_finder.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_finder.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_info.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_info.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/pattern_result.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/detector/pattern_result.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/error_correction_level.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/error_correction_level.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/format_information.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/format_information.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/qrcode_reader.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/qrcode_reader.hpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/version.cpp create mode 100644 modules/wechat_qrcode/src/zxing/qrcode/version.hpp create mode 100644 modules/wechat_qrcode/src/zxing/reader.cpp create mode 100644 modules/wechat_qrcode/src/zxing/reader.hpp create mode 100644 modules/wechat_qrcode/src/zxing/result.cpp create mode 100644 modules/wechat_qrcode/src/zxing/result.hpp create mode 100644 modules/wechat_qrcode/src/zxing/resultpoint.cpp create mode 100644 modules/wechat_qrcode/src/zxing/resultpoint.hpp create mode 100644 modules/wechat_qrcode/src/zxing/zxing.hpp create mode 100644 modules/wechat_qrcode/test/test_main.cpp create mode 100644 modules/wechat_qrcode/test/test_precomp.hpp create mode 100644 modules/wechat_qrcode/test/test_qrcode.cpp diff --git a/README.md b/README.md new file mode 100644 index 0000000..806f7d5 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# OpenCV Contrib +OpenCV的扩展包,目前只包含了一个微信二维码的包 + +## 用途 +1. 支持更好的二维码扫描 + +## 代码修改 +原代码只反回了二维码矩形,未返回二维码方向信息,主要添加此内容 + +## 参考 +原地址: https://github.com/WeChatCV/opencv_contrib \ No newline at end of file diff --git a/modules/wechat_qrcode/CMakeLists.txt b/modules/wechat_qrcode/CMakeLists.txt new file mode 100644 index 0000000..210f4a0 --- /dev/null +++ b/modules/wechat_qrcode/CMakeLists.txt @@ -0,0 +1,30 @@ +set(the_description "WeChat QR code Detector") +ocv_define_module(wechat_qrcode opencv_core opencv_imgproc opencv_dnn WRAP java objc python js) + +# need to change +set(wechat_qrcode_commit_hash "a8b69ccc738421293254aec5ddb38bd523503252") +set(hash_detect_caffemodel "238e2b2d6f3c18d6c3a30de0c31e23cf") +set(hash_detect_prototxt "6fb4976b32695f9f5c6305c19f12537d") +set(hash_sr_caffemodel "cbfcd60361a73beb8c583eea7e8e6664") +set(hash_sr_prototxt "69db99927a70df953b471daaba03fbef") + +set(model_types caffemodel prototxt) +set(model_names detect sr) + +foreach(model_name ${model_names}) + foreach(model_type ${model_types}) + ocv_download(FILENAME ${model_name}.${model_type} + HASH ${hash_${model_name}_${model_type}} + URL + "${OPENCV_WECHAT_QRCODE_URL}" + "$ENV{OPENCV_WECHAT_QRCODE_URL}" + "https://raw.githubusercontent.com/WeChatCV/opencv_3rdparty/${wechat_qrcode_commit_hash}/" + DESTINATION_DIR "${CMAKE_BINARY_DIR}/downloads/wechat_qrcode" + ID "wechat_qrcode" + RELATIVE_URL + STATUS res) + if(NOT res) + message(WARNING "WeChatQRCode: Can't get ${model_name} ${model_type} file for wechat qrcode.") + endif() + endforeach() +endforeach() diff --git a/modules/wechat_qrcode/LICENSE b/modules/wechat_qrcode/LICENSE new file mode 100644 index 0000000..3335826 --- /dev/null +++ b/modules/wechat_qrcode/LICENSE @@ -0,0 +1,253 @@ +Tencent is pleased to support the open source community by making WeChat QRCode available. + +Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +The below software in this distribution may have been modified by THL A29 Limited ("Tencent Modifications"). +All Tencent Modifications are Copyright (C) THL A29 Limited. + +WeChat QRCode is licensed under the Apache License Version 2.0, except for the third-party components listed below. + +Terms of the Apache License Version 2.0 +-------------------------------------------------------------------- + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + + +Other dependencies and licenses: + +Open Source Software Licensed under the Apache License Version 2.0: +-------------------------------------------------------------------- +1. zxing +Copyright (c) zxing authors and contributors +Please note this software may have been modified by Tencent. + +Terms of the Apache License Version 2.0: +-------------------------------------------------------------------- +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and +You must cause any modified files to carry prominent notices stating that You changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/modules/wechat_qrcode/README.md b/modules/wechat_qrcode/README.md new file mode 100644 index 0000000..1da3167 --- /dev/null +++ b/modules/wechat_qrcode/README.md @@ -0,0 +1,12 @@ +WeChat QR code detector for detecting and parsing QR code. +================================================ + +WeChat QR code detector is a high-performance and lightweight QR code detect and decode library, which is contributed by WeChat Computer Vision Team (WeChatCV). It has been widely used in various Tencent applications, including WeChat, WeCom, QQ, QQ Browser, and so on. There are four primary features of WeChat QR code detector: + +1. CNN-based QR code detector. Different from the traditional detector, we introduce a tiny CNN model for multiple code detection. The detector is based on SSD architecture with a MobileNetV2-like backbone, which is run on caffe inference framework. + +2. CNN-based QR code enhancement. To improve the performance of tiny QR code, we design a lighten super-resolution CNN model for QR code, called QRSR. Depth-wise convolution, DenseNet concat and deconvolution are the core techniques in the QRSR model. + +3. More robust finder pattern detection. Besides traditional horizontal line searching, we propose an area size based finder pattern detection method. we calculate the area size of black and white block to locate the finder pattern by the pre-computed connected cells. + +4. Massive engineering optimization. Based on [zing-cpp](https://github.com/glassechidna/zxing-cpp), we conduct massive engineering optimization to boost the decoding success rate, such as trying more binarization methods, supporting N:1:3:1:1 finder pattern detection, finding more alignment pattern, clustering similar size finder pattern, and etc. diff --git a/modules/wechat_qrcode/include/opencv2/wechat_qrcode.hpp b/modules/wechat_qrcode/include/opencv2/wechat_qrcode.hpp new file mode 100644 index 0000000..c2559b2 --- /dev/null +++ b/modules/wechat_qrcode/include/opencv2/wechat_qrcode.hpp @@ -0,0 +1,62 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __OPENCV_WECHAT_QRCODE_HPP__ +#define __OPENCV_WECHAT_QRCODE_HPP__ +#include "opencv2/core.hpp" +/** @defgroup wechat_qrcode WeChat QR code detector for detecting and parsing QR code. + */ +namespace cv { +namespace wechat_qrcode { +//! @addtogroup wechat_qrcode +//! @{ +/** + * @brief WeChat QRCode includes two CNN-based models: + * A object detection model and a super resolution model. + * Object detection model is applied to detect QRCode with the bounding box. + * super resolution model is applied to zoom in QRCode when it is small. + * + */ +class CV_EXPORTS_W WeChatQRCode { +public: + /** + * @brief Initialize the WeChatQRCode. + * It includes two models, which are packaged with caffe format. + * Therefore, there are prototxt and caffe models (In total, four paramenters). + * + * @param detector_prototxt_path prototxt file path for the detector + * @param detector_caffe_model_path caffe model file path for the detector + * @param super_resolution_prototxt_path prototxt file path for the super resolution model + * @param super_resolution_caffe_model_path caffe file path for the super resolution model + */ + CV_WRAP WeChatQRCode(const String& detector_prototxt_path = "", + const String& detector_caffe_model_path = "", + const String& super_resolution_prototxt_path = "", + const String& super_resolution_caffe_model_path = ""); + ~WeChatQRCode(){}; + + /** + * @brief Both detects and decodes QR code. + * To simplify the usage, there is a only API: detectAndDecode + * + * @param img supports grayscale or color (BGR) image. + * @param points optional output array of vertices of the found QR code quadrangle. Will be + * empty if not found. + * @return list of decoded string. + */ + CV_WRAP std::vector detectAndDecode(InputArray img, + OutputArrayOfArrays points = noArray()); + +protected: + class Impl; + Ptr p; +}; + +//! @} +} // namespace wechat_qrcode +} // namespace cv +#endif // __OPENCV_WECHAT_QRCODE_HPP__ diff --git a/modules/wechat_qrcode/samples/qrcode.py b/modules/wechat_qrcode/samples/qrcode.py new file mode 100644 index 0000000..fd79607 --- /dev/null +++ b/modules/wechat_qrcode/samples/qrcode.py @@ -0,0 +1,53 @@ +import cv2 +import sys + +print(sys.argv[0]) +print('A demo program of WeChat QRCode Detector:') +camIdx = -1 +if len(sys.argv) > 1: + if sys.argv[1] == "-camera": + camIdx = int(sys.argv[2]) if len(sys.argv)>2 else 0 + img = cv2.imread(sys.argv[1]) +else: + print(" Usage: " + sys.argv[0] + " ") + exit(0) + +# For python API generator, it follows the template: {module_name}_{class_name}, +# so it is a little weird. +# The model is downloaded to ${CMAKE_BINARY_DIR}/downloads/wechat_qrcode if cmake runs without warnings, +# otherwise you can download them from https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode. +try: + detector = cv2.wechat_qrcode_WeChatQRCode( + "detect.prototxt", "detect.caffemodel", "sr.prototxt", "sr.caffemodel") +except: + print("---------------------------------------------------------------") + print("Failed to initialize WeChatQRCode.") + print("Please, download 'detector.*' and 'sr.*' from") + print("https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode") + print("and put them into the current directory.") + print("---------------------------------------------------------------") + exit(0) + +prevstr = "" + +if camIdx < 0: + res, points = detector.detectAndDecode(img) + print(res,points) +else: + cap = cv2.VideoCapture(camIdx) + while True: + res, img = cap.read() + if img.empty(): + break + res, points = detector.detectAndDecode(img) + for t in res: + if t != prevstr: + print(t) + if res: + prevstr = res[-1] + cv2.imshow("image", img) + if cv2.waitKey(30) >= 0: + break + # When everything done, release the capture + cap.release() + cv2.destroyAllWindows() diff --git a/modules/wechat_qrcode/samples/qrcode_example.cpp b/modules/wechat_qrcode/samples/qrcode_example.cpp new file mode 100644 index 0000000..046525b --- /dev/null +++ b/modules/wechat_qrcode/samples/qrcode_example.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +using namespace std; +using namespace cv; + +#include +int main(int argc, char* argv[]) { + cout << endl << argv[0] << endl << endl; + cout << "A demo program of WeChat QRCode Detector: " << endl; + + Mat img; + int camIdx = -1; + if (argc > 1) { + bool live = strcmp(argv[1], "-camera") == 0; + if (live) { + camIdx = argc > 2 ? atoi(argv[2]) : 0; + } else { + img = imread(argv[1]); + } + } else { + cout << " Usage: " << argv[0] << " " << endl; + return 0; + } + // The model is downloaded to ${CMAKE_BINARY_DIR}/downloads/wechat_qrcode if cmake runs without warnings, + // otherwise you can download them from https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode. + Ptr detector; + + try { + detector = makePtr("detect.prototxt", "detect.caffemodel", + "sr.prototxt", "sr.caffemodel"); + } catch (const std::exception& e) { + cout << + "\n---------------------------------------------------------------\n" + "Failed to initialize WeChatQRCode.\n" + "Please, download 'detector.*' and 'sr.*' from\n" + "https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode\n" + "and put them into the current directory.\n" + "---------------------------------------------------------------\n"; + cout << e.what() << endl; + return 0; + } + string prevstr = ""; + vector points; + + if (camIdx < 0) { + auto res = detector->detectAndDecode(img, points); + for (const auto& t : res) cout << t << endl; + } else { + VideoCapture cap(camIdx); + for(;;) { + cap >> img; + if (img.empty()) + break; + auto res = detector->detectAndDecode(img, points); + for (const auto& t : res) { + if (t != prevstr) + cout << t << endl; + } + if (!res.empty()) + prevstr = res.back(); + imshow("image", img); + if (waitKey(30) >= 0) + break; + } + } + return 0; +} \ No newline at end of file diff --git a/modules/wechat_qrcode/src/binarizermgr.cpp b/modules/wechat_qrcode/src/binarizermgr.cpp new file mode 100644 index 0000000..db019cd --- /dev/null +++ b/modules/wechat_qrcode/src/binarizermgr.cpp @@ -0,0 +1,70 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "precomp.hpp" +#include "binarizermgr.hpp" +#include "imgsource.hpp" + + +using zxing::Binarizer; +using zxing::LuminanceSource; +namespace cv { +namespace wechat_qrcode { +BinarizerMgr::BinarizerMgr() : m_iNowRotateIndex(0), m_iNextOnceBinarizer(-1) { + m_vecRotateBinarizer.push_back(Hybrid); + m_vecRotateBinarizer.push_back(FastWindow); + m_vecRotateBinarizer.push_back(SimpleAdaptive); + m_vecRotateBinarizer.push_back(AdaptiveThreshold); +} + +BinarizerMgr::~BinarizerMgr() {} + +zxing::Ref BinarizerMgr::Binarize(zxing::Ref source) { + BINARIZER binarizerIdx = m_vecRotateBinarizer[m_iNowRotateIndex]; + if (m_iNextOnceBinarizer >= 0) { + binarizerIdx = (BINARIZER)m_iNextOnceBinarizer; + } + + zxing::Ref binarizer; + switch (binarizerIdx) { + case Hybrid: + binarizer = new zxing::HybridBinarizer(source); + break; + case FastWindow: + binarizer = new zxing::FastWindowBinarizer(source); + break; + case SimpleAdaptive: + binarizer = new zxing::SimpleAdaptiveBinarizer(source); + break; + case AdaptiveThreshold: + binarizer = new zxing::AdaptiveThresholdMeanBinarizer(source); + break; + default: + binarizer = new zxing::HybridBinarizer(source); + break; + } + + return binarizer; +} + +void BinarizerMgr::SwitchBinarizer() { + m_iNowRotateIndex = (m_iNowRotateIndex + 1) % m_vecRotateBinarizer.size(); +} + +int BinarizerMgr::GetCurBinarizer() { + if (m_iNextOnceBinarizer != -1) return m_iNextOnceBinarizer; + return m_vecRotateBinarizer[m_iNowRotateIndex]; +} + +void BinarizerMgr::SetNextOnceBinarizer(int iBinarizerIndex) { + m_iNextOnceBinarizer = iBinarizerIndex; +} + +void BinarizerMgr::SetBinarizer(vector vecRotateBinarizer) { + m_vecRotateBinarizer = vecRotateBinarizer; +} +} // namespace wechat_qrcode +} // namespace cv \ No newline at end of file diff --git a/modules/wechat_qrcode/src/binarizermgr.hpp b/modules/wechat_qrcode/src/binarizermgr.hpp new file mode 100644 index 0000000..a8a1e4b --- /dev/null +++ b/modules/wechat_qrcode/src/binarizermgr.hpp @@ -0,0 +1,51 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __OPENCV_WECHAT_QRCODE_BINARIZERMGR_HPP__ +#define __OPENCV_WECHAT_QRCODE_BINARIZERMGR_HPP__ + +#include "zxing/binarizer.hpp" +#include "zxing/common/binarizer/adaptive_threshold_mean_binarizer.hpp" +#include "zxing/common/counted.hpp" +#include "zxing/common/binarizer/fast_window_binarizer.hpp" +#include "zxing/common/binarizer/hybrid_binarizer.hpp" +#include "zxing/common/binarizer/simple_adaptive_binarizer.hpp" +#include "zxing/zxing.hpp" + +namespace cv { +namespace wechat_qrcode { +class BinarizerMgr { +public: + enum BINARIZER { + Hybrid = 0, + FastWindow = 1, + SimpleAdaptive = 2, + AdaptiveThreshold = 3 + }; + +public: + BinarizerMgr(); + ~BinarizerMgr(); + + zxing::Ref Binarize(zxing::Ref source); + + void SwitchBinarizer(); + + int GetCurBinarizer(); + + void SetNextOnceBinarizer(int iBinarizerIndex); + + void SetBinarizer(vector vecRotateBinarizer); + +private: + int m_iNowRotateIndex; + int m_iNextOnceBinarizer; + vector m_vecRotateBinarizer; +}; +} // namespace wechat_qrcode +} // namespace cv +#endif // __OPENCV_WECHAT_QRCODE_BINARIZERMGR_HPP__ diff --git a/modules/wechat_qrcode/src/decodermgr.cpp b/modules/wechat_qrcode/src/decodermgr.cpp new file mode 100644 index 0000000..5e7b72a --- /dev/null +++ b/modules/wechat_qrcode/src/decodermgr.cpp @@ -0,0 +1,103 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "precomp.hpp" +#include "decodermgr.hpp" + + +using zxing::ArrayRef; +using zxing::BinaryBitmap; +using zxing::DecodeHints; +using zxing::ErrorHandler; +using zxing::LuminanceSource; +using zxing::Ref; +using zxing::Result; +using zxing::UnicomBlock; +namespace cv { +namespace wechat_qrcode { +int DecoderMgr::decodeImage(cv::Mat src, bool use_nn_detector, string& result, int pos[4][2]) { + int width = src.cols; + int height = src.rows; + if (width <= 20 || height <= 20) + return -1; // image data is not enough for providing reliable results + + std::vector scaled_img_data(src.data, src.data + width * height); + zxing::ArrayRef scaled_img_zx = + zxing::ArrayRef(new zxing::Array(scaled_img_data)); + + zxing::Ref zx_result; + + decode_hints_.setUseNNDetector(use_nn_detector); + + Ref source; + qbarUicomBlock_ = new UnicomBlock(width, height); + + // Four Binarizers + int tryBinarizeTime = 4; + for (int tb = 0; tb < tryBinarizeTime; tb++) { + if (source == NULL || height * width > source->getMaxSize()) { + source = ImgSource::create(scaled_img_zx.data(), width, height); + } else { + source->reset(scaled_img_zx.data(), width, height); + } + int ret = TryDecode(source, zx_result); + if (!ret) { + result = zx_result->getText()->getText(); + ArrayRef> pps = zx_result->getResultPoints(); + for (int i = 0; pps && i < pps->size() && i < 4; i++) { + pos[i][0] = pps[i]->getX(); + pos[i][1] = pps[i]->getY(); + } + return ret; + } + // try different binarizers + binarizer_mgr_.SwitchBinarizer(); + } + return -1; +} + +int DecoderMgr::TryDecode(Ref source, Ref& result) { + int res = -1; + string cell_result; + + // get binarizer + zxing::Ref binarizer = binarizer_mgr_.Binarize(source); + zxing::Ref binary_bitmap(new BinaryBitmap(binarizer)); + binary_bitmap->m_poUnicomBlock = qbarUicomBlock_; + + result = Decode(binary_bitmap, decode_hints_); + res = (result == NULL) ? 1 : 0; + + //if (result) { + // ArrayRef> pps = result->getResultPoints(); + // if (!pps) { + // //printf("Nony: %d\n", pps->size());*/ + // printf("It is null................\n"); + // } + // else { + // printf("It is not null................\n"); + // printf("Nony: %d\n", pps->size()); + // for (int i = 0; i < pps->size(); i++) { + // Ref rr = pps[i]; + // printf("Nony: %f, %f\n", rr->getX(), rr->getY()); + // } + // } + //} + + + + if (res == 0) { + result->setBinaryMethod(int(binarizer_mgr_.GetCurBinarizer())); + } + + return res; +} + +Ref DecoderMgr::Decode(Ref image, DecodeHints hints) { + return reader_->decode(image, hints); +} +} // namespace wechat_qrcode +} // namespace cv \ No newline at end of file diff --git a/modules/wechat_qrcode/src/decodermgr.hpp b/modules/wechat_qrcode/src/decodermgr.hpp new file mode 100644 index 0000000..e39273c --- /dev/null +++ b/modules/wechat_qrcode/src/decodermgr.hpp @@ -0,0 +1,46 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __OPENCV_WECHAT_QRCODE_DECODERMGR_HPP__ +#define __OPENCV_WECHAT_QRCODE_DECODERMGR_HPP__ + +// zxing +#include "zxing/binarizer.hpp" +#include "zxing/binarybitmap.hpp" +#include "zxing/decodehints.hpp" +#include "zxing/qrcode/qrcode_reader.hpp" +#include "zxing/result.hpp" + +// qbar +#include "binarizermgr.hpp" +#include "imgsource.hpp" +namespace cv { +namespace wechat_qrcode { + +class DecoderMgr { +public: + DecoderMgr() { reader_ = new zxing::qrcode::QRCodeReader(); }; + ~DecoderMgr(){}; + + int decodeImage(cv::Mat src, bool use_nn_detector, string& result, int pos[4][2]); + +private: + zxing::Ref qbarUicomBlock_; + zxing::DecodeHints decode_hints_; + + zxing::Ref reader_; + BinarizerMgr binarizer_mgr_; + + zxing::Ref Decode(zxing::Ref image, + zxing::DecodeHints hints); + + int TryDecode(zxing::Ref source, zxing::Ref& result); +}; + +} // namespace wechat_qrcode +} // namespace cv +#endif // __OPENCV_WECHAT_QRCODE_DECODERMGR_HPP__ diff --git a/modules/wechat_qrcode/src/detector/align.cpp b/modules/wechat_qrcode/src/detector/align.cpp new file mode 100644 index 0000000..05ab533 --- /dev/null +++ b/modules/wechat_qrcode/src/detector/align.cpp @@ -0,0 +1,66 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "../precomp.hpp" +#include "align.hpp" + +using std::max; +using std::min; +namespace cv { +namespace wechat_qrcode { +Align::Align() { rotate90_ = false; } + +Mat Align::calcWarpMatrix(const Mat src, const Mat dst) { + M_ = getPerspectiveTransform(src, dst); + M_inv_ = M_.inv(); + return M_; +} + +vector Align::warpBack(const vector &dst_pts) { + vector src_pts; + for (size_t j = 0; j < dst_pts.size(); j++) { + auto src_x = (rotate90_ ? dst_pts[j].y : dst_pts[j].x) + crop_x_; + auto src_y = (rotate90_ ? dst_pts[j].x : dst_pts[j].y) + crop_y_; + src_pts.push_back(Point2f(src_x, src_y)); + } + return src_pts; +} + +Mat Align::crop(const Mat &inputImg, const int width, const int height) { + Mat warp_dst = Mat::zeros(height, width, inputImg.type()); + + warpPerspective(inputImg, warp_dst, M_, warp_dst.size(), INTER_LINEAR, BORDER_CONSTANT, 255); + + return warp_dst; +} + +Mat Align::crop(const Mat &inputImg, const Mat &srcPts, const float paddingW, const float paddingH, + const int minPadding) { + int x0 = srcPts.at(0, 0); + int y0 = srcPts.at(0, 1); + int x2 = srcPts.at(2, 0); + int y2 = srcPts.at(2, 1); + + int width = x2 - x0 + 1; + int height = y2 - y0 + 1; + + int padx = max(paddingW * width, static_cast(minPadding)); + int pady = max(paddingH * height, static_cast(minPadding)); + + crop_x_ = max(x0 - padx, 0); + crop_y_ = max(y0 - pady, 0); + int end_x = min(x2 + padx, inputImg.cols - 1); + int end_y = min(y2 + pady, inputImg.rows - 1); + + Rect crop_roi(crop_x_, crop_y_, end_x - crop_x_ + 1, end_y - crop_y_ + 1); + + Mat dst = inputImg(crop_roi).clone(); + if (rotate90_) dst = dst.t(); // transpose + return dst; +} + +} // namespace wechat_qrcode +} // namespace cv diff --git a/modules/wechat_qrcode/src/detector/align.hpp b/modules/wechat_qrcode/src/detector/align.hpp new file mode 100644 index 0000000..2ad88a5 --- /dev/null +++ b/modules/wechat_qrcode/src/detector/align.hpp @@ -0,0 +1,41 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __DETECTOR_ALIGN_HPP_ +#define __DETECTOR_ALIGN_HPP_ + +#include +#include +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" + +namespace cv { +namespace wechat_qrcode { + +class Align { +public: + Align(); + Mat calcWarpMatrix(const Mat src, const Mat dst); + std::vector warpBack(const std::vector &dst_pts); + Mat crop(const Mat &inputImg, const Mat &srcPts, const float paddingW, const float paddingH, + const int minPadding); + + void setRotate90(bool v) { rotate90_ = v; } + +private: + Mat crop(const Mat &inputImg, const int width, const int height); + Mat M_; + Mat M_inv_; + + int crop_x_; + int crop_y_; + bool rotate90_; +}; + +} // namespace wechat_qrcode +} // namespace cv +#endif // __DETECTOR_ALIGN_HPP_ diff --git a/modules/wechat_qrcode/src/detector/ssd_detector.cpp b/modules/wechat_qrcode/src/detector/ssd_detector.cpp new file mode 100644 index 0000000..dca1851 --- /dev/null +++ b/modules/wechat_qrcode/src/detector/ssd_detector.cpp @@ -0,0 +1,57 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "../precomp.hpp" +#include "ssd_detector.hpp" +#define CLIP(x, x1, x2) max(x1, min(x, x2)) +namespace cv { +namespace wechat_qrcode { +int SSDDetector::init(const string& proto_path, const string& model_path) { + net_ = dnn::readNetFromCaffe(proto_path, model_path); + return 0; +} + +vector SSDDetector::forward(Mat img, const int target_width, const int target_height) { + int img_w = img.cols; + int img_h = img.rows; + Mat input; + resize(img, input, Size(target_width, target_height), 0, 0, INTER_CUBIC); + + dnn::blobFromImage(input, input, 1.0 / 255, Size(input.cols, input.rows), {0.0f, 0.0f, 0.0f}, + false, false); + net_.setInput(input, "data"); + + auto prob = net_.forward("detection_output"); + vector point_list; + // the shape is (1,1,100,7)=>(batch,channel,count,dim) + for (int row = 0; row < prob.size[2]; row++) { + const float* prob_score = prob.ptr(0, 0, row); + // prob_score[0] is not used. + // prob_score[1]==1 stands for qrcode + if (prob_score[1] == 1 && prob_score[2] > 1E-5) { + // add a safe score threshold due to https://github.com/opencv/opencv_contrib/issues/2877 + // prob_score[2] is the probability of the qrcode, which is not used. + auto point = Mat(4, 2, CV_32FC1); + float x0 = CLIP(prob_score[3] * img_w, 0.0f, img_w - 1.0f); + float y0 = CLIP(prob_score[4] * img_h, 0.0f, img_h - 1.0f); + float x1 = CLIP(prob_score[5] * img_w, 0.0f, img_w - 1.0f); + float y1 = CLIP(prob_score[6] * img_h, 0.0f, img_h - 1.0f); + + point.at(0, 0) = x0; + point.at(0, 1) = y0; + point.at(1, 0) = x1; + point.at(1, 1) = y0; + point.at(2, 0) = x1; + point.at(2, 1) = y1; + point.at(3, 0) = x0; + point.at(3, 1) = y1; + point_list.push_back(point); + } + } + return point_list; +} +} // namespace wechat_qrcode +} // namespace cv \ No newline at end of file diff --git a/modules/wechat_qrcode/src/detector/ssd_detector.hpp b/modules/wechat_qrcode/src/detector/ssd_detector.hpp new file mode 100644 index 0000000..e510cb3 --- /dev/null +++ b/modules/wechat_qrcode/src/detector/ssd_detector.hpp @@ -0,0 +1,31 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __DETECTOR_SSD_DETECTOR_HPP_ +#define __DETECTOR_SSD_DETECTOR_HPP_ + +#include + +#include "opencv2/dnn.hpp" +#include "opencv2/imgproc.hpp" +namespace cv { +namespace wechat_qrcode { + +class SSDDetector { +public: + SSDDetector(){}; + ~SSDDetector(){}; + int init(const std::string& proto_path, const std::string& model_path); + std::vector forward(Mat img, const int target_width, const int target_height); + +private: + dnn::Net net_; +}; + +} // namespace wechat_qrcode +} // namespace cv +#endif // __DETECTOR_SSD_DETECTOR_HPP_ diff --git a/modules/wechat_qrcode/src/imgsource.cpp b/modules/wechat_qrcode/src/imgsource.cpp new file mode 100644 index 0000000..bd49de4 --- /dev/null +++ b/modules/wechat_qrcode/src/imgsource.cpp @@ -0,0 +1,188 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "precomp.hpp" +#include "imgsource.hpp" + +using zxing::ArrayRef; +using zxing::ByteMatrix; +using zxing::ErrorHandler; +using zxing::LuminanceSource; +using zxing::Ref; +namespace cv { +namespace wechat_qrcode { + +// Initialize the ImgSource +ImgSource::ImgSource(unsigned char* pixels, int width, int height) + : Super(width, height) { + luminances = new unsigned char[width * height]; + memset(luminances, 0, width * height); + + rgbs = pixels; + + dataWidth = width; + dataHeight = height; + left = 0; + top = 0; + + // Make gray luminances first + makeGray(); +} + +// Added for crop function +ImgSource::ImgSource(unsigned char* pixels, int width, int height, int left_, int top_, + int cropWidth, int cropHeight, + ErrorHandler& err_handler) + : Super(cropWidth, cropHeight) { + rgbs = pixels; + + dataWidth = width; + dataHeight = height; + left = left_; + top = top_; + + // super(width, height); + if ((left_ + cropWidth) > dataWidth || (top_ + cropHeight) > dataHeight || top_ < 0 || + left_ < 0) { + err_handler = + zxing::IllegalArgumentErrorHandler("Crop rectangle does not fit within image data."); + return; + } + + luminances = new unsigned char[width * height]; + + // Make gray luminances first + makeGray(); +} + +ImgSource::~ImgSource() { + if (luminances != NULL) { + delete[] luminances; + } +} + +Ref ImgSource::create(unsigned char* pixels, int width, int height) { + return Ref(new ImgSource(pixels, width, height)); +} + +Ref ImgSource::create(unsigned char* pixels, int width, int height, int left, int top, + int cropWidth, int cropHeight, + zxing::ErrorHandler& err_handler) { + return Ref(new ImgSource(pixels, width, height, left, top, cropWidth, cropHeight, err_handler)); +} + +void ImgSource::reset(unsigned char* pixels, int width, int height) { + rgbs = pixels; + left = 0; + top = 0; + + setWidth(width); + setHeight(height); + dataWidth = width; + dataHeight = height; + makeGrayReset(); +} + +ArrayRef ImgSource::getRow(int y, zxing::ArrayRef row, + zxing::ErrorHandler& err_handler) const { + if (y < 0 || y >= getHeight()) { + err_handler = zxing::IllegalArgumentErrorHandler("Requested row is outside the image"); + return ArrayRef(); + } + + int width = getWidth(); + if (row->data() == NULL || row->empty() || row->size() < width) { + row = zxing::ArrayRef(width); + } + int offset = (y + top) * dataWidth + left; + + char* rowPtr = &row[0]; + arrayCopy(luminances, offset, rowPtr, 0, width); + + return row; +} + +/** This is a more efficient implementation. */ +ArrayRef ImgSource::getMatrix() const { + int width = getWidth(); + int height = getHeight(); + + int area = width * height; + + // 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 _matrix; + } + + zxing::ArrayRef newMatrix = zxing::ArrayRef(area); + + int inputOffset = top * dataWidth + left; + + // If the width matches the full width of the underlying data, perform a + // single copy. + if (width == dataWidth) { + arrayCopy(luminances, inputOffset, &newMatrix[0], 0, area); + return newMatrix; + } + + // Otherwise copy one cropped row at a time. + for (int y = 0; y < height; y++) { + int outputOffset = y * width; + arrayCopy(luminances, inputOffset, &newMatrix[0], outputOffset, width); + inputOffset += dataWidth; + } + return newMatrix; +} + + +void ImgSource::makeGray() { + int area = dataWidth * dataHeight; + _matrix = zxing::ArrayRef(area); + arrayCopy(rgbs, 0, &_matrix[0], 0, area); +} + +void ImgSource::makeGrayReset() { + int area = dataWidth * dataHeight; + arrayCopy(rgbs, 0, &_matrix[0], 0, area); +} + +void ImgSource::arrayCopy(unsigned char* src, int inputOffset, char* dst, int outputOffset, + int length) const { + const unsigned char* srcPtr = src + inputOffset; + char* dstPtr = dst + outputOffset; + + memcpy(dstPtr, srcPtr, length * sizeof(unsigned char)); +} + +bool ImgSource::isCropSupported() const { return true; } + +Ref ImgSource::crop(int left_, int top_, int width, int height, + ErrorHandler& err_handler) const { + return ImgSource::create(rgbs, dataWidth, dataHeight, left + left_, top + top_, width, height, err_handler); +} + +bool ImgSource::isRotateSupported() const { return false; } + +Ref ImgSource::rotateCounterClockwise(ErrorHandler& err_handler) const { + // Intentionally flip the left, top, width, and height arguments as + // needed. dataWidth and dataHeight are always kept unrotated. + int width = getWidth(); + int height = getHeight(); + + return ImgSource::create(rgbs, dataWidth, dataHeight, top, left, height, width, err_handler); +} + + +Ref ImgSource::getByteMatrix() const { + return Ref(new ByteMatrix(getWidth(), getHeight(), getMatrix())); +} +} // namespace wechat_qrcode +} // namespace cv \ No newline at end of file diff --git a/modules/wechat_qrcode/src/imgsource.hpp b/modules/wechat_qrcode/src/imgsource.hpp new file mode 100644 index 0000000..ec62dc6 --- /dev/null +++ b/modules/wechat_qrcode/src/imgsource.hpp @@ -0,0 +1,63 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __OPENCV_WECHAT_QRCODE_IMGSOURCE_HPP__ +#define __OPENCV_WECHAT_QRCODE_IMGSOURCE_HPP__ +#include "zxing/common/bytematrix.hpp" +#include "zxing/errorhandler.hpp" +#include "zxing/luminance_source.hpp" +namespace cv { +namespace wechat_qrcode { +class ImgSource : public zxing::LuminanceSource { +private: + typedef LuminanceSource Super; + zxing::ArrayRef _matrix; + unsigned char* rgbs; + unsigned char* luminances; + int dataWidth; + int dataHeight; + int left; + int top; + void makeGray(); + void makeGrayReset(); + + void arrayCopy(unsigned char* src, int inputOffset, char* dst, int outputOffset, + int length) const; + + + ~ImgSource(); + +public: + ImgSource(unsigned char* pixels, int width, int height); + ImgSource(unsigned char* pixels, int width, int height, int left, int top, int cropWidth, + int cropHeight, zxing::ErrorHandler& err_handler); + + static zxing::Ref create(unsigned char* pixels, int width, int height); + static zxing::Ref create(unsigned char* pixels, int width, int height, int left, + int top, int cropWidth, int cropHeight, zxing::ErrorHandler& err_handler); + void reset(unsigned char* pixels, int width, int height); + zxing::ArrayRef getRow(int y, zxing::ArrayRef row, + zxing::ErrorHandler& err_handler) const override; + zxing::ArrayRef getMatrix() const override; + zxing::Ref getByteMatrix() const override; + + bool isCropSupported() const override; + zxing::Ref crop(int left, int top, int width, int height, + zxing::ErrorHandler& err_handler) const override; + + bool isRotateSupported() const override; + zxing::Ref rotateCounterClockwise( + zxing::ErrorHandler& err_handler) const override; + + int getMaxSize() { return dataHeight * dataWidth; } +}; +} // namespace wechat_qrcode +} // namespace cv +#endif // __OPENCV_WECHAT_QRCODE_IMGSOURCE_HPP__ diff --git a/modules/wechat_qrcode/src/precomp.hpp b/modules/wechat_qrcode/src/precomp.hpp new file mode 100644 index 0000000..1fc21c0 --- /dev/null +++ b/modules/wechat_qrcode/src/precomp.hpp @@ -0,0 +1,29 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __OPENCV_WECHAT_QRCODE_PRECOMP_HPP__ +#define __OPENCV_WECHAT_QRCODE_PRECOMP_HPP__ +#ifdef _MSC_VER +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "imgsource.hpp" +using std::ostringstream; +using std::string; +using std::vector; +#endif // __OPENCV_WECHAT_QRCODE_PRECOMP_HPP__ diff --git a/modules/wechat_qrcode/src/scale/super_scale.cpp b/modules/wechat_qrcode/src/scale/super_scale.cpp new file mode 100644 index 0000000..8b3b113 --- /dev/null +++ b/modules/wechat_qrcode/src/scale/super_scale.cpp @@ -0,0 +1,63 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "../precomp.hpp" +#include "super_scale.hpp" + + +#define CLIP(x, x1, x2) max(x1, min(x, x2)) +namespace cv { +namespace wechat_qrcode { +int SuperScale::init(const std::string &proto_path, const std::string &model_path) { + srnet_ = dnn::readNetFromCaffe(proto_path, model_path); + net_loaded_ = true; + return 0; +} + +Mat SuperScale::processImageScale(const Mat &src, float scale, const bool &use_sr, + int sr_max_size) { + Mat dst = src; + if (scale == 1.0) { // src + return dst; + } + + int width = src.cols; + int height = src.rows; + if (scale == 2.0) { // upsample + int SR_TH = sr_max_size; + if (use_sr && (int)sqrt(width * height * 1.0) < SR_TH && net_loaded_) { + int ret = superResoutionScale(src, dst); + if (ret == 0) return dst; + } + + { resize(src, dst, Size(), scale, scale, INTER_CUBIC); } + } else if (scale < 1.0) { // downsample + resize(src, dst, Size(), scale, scale, INTER_AREA); + } + + return dst; +} + +int SuperScale::superResoutionScale(const Mat &src, Mat &dst) { + Mat blob; + dnn::blobFromImage(src, blob, 1.0 / 255, Size(src.cols, src.rows), {0.0f}, false, false); + + srnet_.setInput(blob); + auto prob = srnet_.forward(); + + dst = Mat(prob.size[2], prob.size[3], CV_8UC1); + + for (int row = 0; row < prob.size[2]; row++) { + const float *prob_score = prob.ptr(0, 0, row); + for (int col = 0; col < prob.size[3]; col++) { + float pixel = prob_score[col] * 255.0; + dst.at(row, col) = static_cast(CLIP(pixel, 0.0f, 255.0f)); + } + } + return 0; +} +} // namespace wechat_qrcode +} // namespace cv \ No newline at end of file diff --git a/modules/wechat_qrcode/src/scale/super_scale.hpp b/modules/wechat_qrcode/src/scale/super_scale.hpp new file mode 100644 index 0000000..2717932 --- /dev/null +++ b/modules/wechat_qrcode/src/scale/super_scale.hpp @@ -0,0 +1,32 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __SCALE_SUPER_SCALE_HPP_ +#define __SCALE_SUPER_SCALE_HPP_ + +#include +#include "opencv2/dnn.hpp" +#include "opencv2/imgproc.hpp" +namespace cv { +namespace wechat_qrcode { + +class SuperScale { +public: + SuperScale(){}; + ~SuperScale(){}; + int init(const std::string &proto_path, const std::string &model_path); + Mat processImageScale(const Mat &src, float scale, const bool &use_sr, int sr_max_size = 160); + +private: + dnn::Net srnet_; + bool net_loaded_ = false; + int superResoutionScale(const cv::Mat &src, cv::Mat &dst); +}; + +} // namespace wechat_qrcode +} // namespace cv +#endif // __SCALE_SUPER_SCALE_HPP_ diff --git a/modules/wechat_qrcode/src/wechat_qrcode.cpp b/modules/wechat_qrcode/src/wechat_qrcode.cpp new file mode 100644 index 0000000..4b99148 --- /dev/null +++ b/modules/wechat_qrcode/src/wechat_qrcode.cpp @@ -0,0 +1,221 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "precomp.hpp" +#include "opencv2/wechat_qrcode.hpp" +#include "decodermgr.hpp" +#include "detector/align.hpp" +#include "detector/ssd_detector.hpp" +#include "opencv2/core.hpp" +#include "opencv2/core/utils/filesystem.hpp" +#include "scale/super_scale.hpp" +#include "zxing/result.hpp" +namespace cv { +namespace wechat_qrcode { +class WeChatQRCode::Impl { +public: + Impl() {} + ~Impl() {} + /** + * @brief detect QR codes from the given image + * + * @param img supports grayscale or color (BGR) image. + * @return vector detected QR code bounding boxes. + */ + std::vector detect(const Mat& img); + /** + * @brief decode QR codes from detected points + * + * @param img supports grayscale or color (BGR) image. + * @param candidate_points detected points. we name it "candidate points" which means no + * all the qrcode can be decoded. + * @param points succussfully decoded qrcode with bounding box points. + * @return vector + */ + std::vector decode(const Mat& img, std::vector& candidate_points, + std::vector& points); + int applyDetector(const Mat& img, std::vector& points); + Mat cropObj(const Mat& img, const Mat& point, Align& aligner); + std::vector getScaleList(const int width, const int height); + std::shared_ptr detector_; + std::shared_ptr super_resolution_model_; + bool use_nn_detector_, use_nn_sr_; +}; + +WeChatQRCode::WeChatQRCode(const String& detector_prototxt_path, + const String& detector_caffe_model_path, + const String& super_resolution_prototxt_path, + const String& super_resolution_caffe_model_path) { + p = makePtr(); + if (!detector_caffe_model_path.empty() && !detector_prototxt_path.empty()) { + // initialize detector model (caffe) + p->use_nn_detector_ = true; + //CV_Assert(utils::fs::exists(detector_prototxt_path)); + //CV_Assert(utils::fs::exists(detector_caffe_model_path)); + p->detector_ = make_shared(); + auto ret = p->detector_->init(detector_prototxt_path, detector_caffe_model_path); + CV_Assert(ret == 0); + } else { + p->use_nn_detector_ = false; + p->detector_ = NULL; + } + // initialize super_resolution_model + // it could also support non model weights by cubic resizing + // so, we initialize it first. + p->super_resolution_model_ = make_shared(); + if (!super_resolution_prototxt_path.empty() && !super_resolution_caffe_model_path.empty()) { + p->use_nn_sr_ = true; + // initialize dnn model (caffe format) + //CV_Assert(utils::fs::exists(super_resolution_prototxt_path)); + //CV_Assert(utils::fs::exists(super_resolution_caffe_model_path)); + auto ret = p->super_resolution_model_->init(super_resolution_prototxt_path, + super_resolution_caffe_model_path); + CV_Assert(ret == 0); + } else { + p->use_nn_sr_ = false; + } +} + +std::vector WeChatQRCode::detectAndDecode(InputArray img, OutputArrayOfArrays points) { + CV_Assert(!img.empty()); + CV_CheckDepthEQ(img.depth(), CV_8U, ""); + + if (img.cols() <= 20 || img.rows() <= 20) { + return vector(); // image data is not enough for providing reliable results + } + Mat input_img; + int incn = img.channels(); + CV_Check(incn, incn == 1 || incn == 3 || incn == 4, ""); + if (incn == 3 || incn == 4) { + cvtColor(img, input_img, COLOR_BGR2GRAY); + } else { + input_img = img.getMat(); + } + auto candidate_points = p->detect(input_img); + auto res_points = vector(); + auto ret = p->decode(input_img, candidate_points, res_points); + // opencv type convert + vector tmp_points; + if (points.needed()) { + for (size_t i = 0; i < res_points.size(); i++) { + Mat tmp_point; + tmp_points.push_back(tmp_point); + res_points[i].convertTo(((OutputArray)tmp_points[i]), CV_32FC2); + } + points.createSameSize(tmp_points, CV_32FC2); + points.assign(tmp_points); + } + return ret; +}; + +vector WeChatQRCode::Impl::decode(const Mat& img, vector& candidate_points, + vector& points) { + if (candidate_points.size() == 0) { + return vector(); + } + vector decode_results; + for (auto& point : candidate_points) { + Mat cropped_img; + if (use_nn_detector_) { + Align aligner; + cropped_img = cropObj(img, point, aligner); + } else { + cropped_img = img; + } + // scale_list contains different scale ratios + auto scale_list = getScaleList(cropped_img.cols, cropped_img.rows); + for (auto cur_scale : scale_list) { + Mat scaled_img = + super_resolution_model_->processImageScale(cropped_img, cur_scale, use_nn_sr_); + string result; + DecoderMgr decodemgr; + + int pos[4][2] = { {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + auto ret = decodemgr.decodeImage(scaled_img, use_nn_detector_, result, pos); + + if (ret == 0) { + decode_results.push_back(result); + points.push_back(point); + float* data = point.ptr(0); + float x = *data; + float y = *(data + 1); + + float oH = abs(point.at(0, 1) - point.at(3, 1)); + float oW = abs(point.at(0, 0) - point.at(1, 0)); + float wRatio = (float)oW / scaled_img.cols; + float hRatio = (float)oH / scaled_img.rows; + + cv::Mat mat(4, 2, CV_32FC1); + + for (int i = 0; i < 4; i++) { + float* dd = mat.ptr(i); + *dd = pos[i][0] * wRatio + x; + *(dd + 1) = pos[i][1] * hRatio + y; + } + points.push_back(mat); + break; + } + } + } + + return decode_results; +} + +vector WeChatQRCode::Impl::detect(const Mat& img) { + auto points = vector(); + + if (use_nn_detector_) { + // use cnn detector + auto ret = applyDetector(img, points); + CV_Assert(ret == 0); + } else { + auto width = img.cols, height = img.rows; + // if there is no detector, use the full image as input + auto point = Mat(4, 2, CV_32FC1); + point.at(0, 0) = 0; + point.at(0, 1) = 0; + point.at(1, 0) = width - 1; + point.at(1, 1) = 0; + point.at(2, 0) = width - 1; + point.at(2, 1) = height - 1; + point.at(3, 0) = 0; + point.at(3, 1) = height - 1; + points.push_back(point); + } + return points; +} + +int WeChatQRCode::Impl::applyDetector(const Mat& img, vector& points) { + int img_w = img.cols; + int img_h = img.rows; + + // hard code input size + int minInputSize = 400; + float resizeRatio = sqrt(img_w * img_h * 1.0 / (minInputSize * minInputSize)); + int detect_width = img_w / resizeRatio; + int detect_height = img_h / resizeRatio; + + points = detector_->forward(img, detect_width, detect_height); + + return 0; +} + +Mat WeChatQRCode::Impl::cropObj(const Mat& img, const Mat& point, Align& aligner) { + // make some padding to boost the qrcode details recall. + float padding_w = 0.1f, padding_h = 0.1f; + auto min_padding = 15; + auto cropped = aligner.crop(img, point, padding_w, padding_h, min_padding); + return cropped; +} + +// empirical rules +vector WeChatQRCode::Impl::getScaleList(const int width, const int height) { + if (width < 320 || height < 320) return {1.0, 2.0, 0.5}; + if (width < 640 && height < 640) return {1.0, 0.5}; + return {0.5, 1.0}; +} +} // namespace wechat_qrcode +} // namespace cv diff --git a/modules/wechat_qrcode/src/zxing/binarizer.cpp b/modules/wechat_qrcode/src/zxing/binarizer.cpp new file mode 100644 index 0000000..6850828 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/binarizer.cpp @@ -0,0 +1,88 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../precomp.hpp" +#include "binarizer.hpp" + +namespace zxing { + +Binarizer::Binarizer(Ref source) : source_(source) { + dataWidth = source->getWidth(); + dataHeight = source->getHeight(); + + width = dataWidth; + height = dataHeight; + + matrix_ = NULL; + matrix0_ = NULL; + matrixInverted_ = NULL; + + histogramBinarized = false; + usingHistogram = false; +} + +Binarizer::~Binarizer() {} + +Ref Binarizer::getLuminanceSource() const { return source_; } + +int Binarizer::getWidth() const { + return width; +} + +int Binarizer::getHeight() const { + return height; +} + +int Binarizer::rotateCounterClockwise() { return 0; } + +int Binarizer::rotateCounterClockwise45() { return 0; } + +Ref Binarizer::getInvertedMatrix(ErrorHandler& err_handler) { + if (!matrix_) { + return Ref(); + } + + if (matrixInverted_ == NULL) { + matrixInverted_ = new BitMatrix(matrix_->getWidth(), matrix_->getHeight(), err_handler); + matrixInverted_->copyOf(matrix_, err_handler); + matrixInverted_->flipAll(); + } + + return matrixInverted_; +} + +// Return different black matrix according to cacheMode +Ref Binarizer::getBlackMatrix(ErrorHandler& err_handler) { + if (err_handler.ErrCode()) return Ref(); + matrix_ = matrix0_; + return matrix_; +} + +Ref Binarizer::getBlackRow(int y, Ref row, ErrorHandler& err_handler) { + if (!matrix_) { + matrix_ = getBlackMatrix(err_handler); + if (err_handler.ErrCode()) return Ref(); + } + + matrix_->getRow(y, row); + return row; +} + +ArrayRef Binarizer::getBlockArray(int size) { + ArrayRef blocks(new Array(size)); + + for (int i = 0; i < blocks->size(); i++) { + blocks[i].sum = 0; + blocks[i].min = 0xFF; + blocks[i].max = 0; + } + + return blocks; +} +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/binarizer.hpp b/modules/wechat_qrcode/src/zxing/binarizer.hpp new file mode 100644 index 0000000..7e444c5 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/binarizer.hpp @@ -0,0 +1,88 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_BINARIZER_HPP__ +#define __ZXING_BINARIZER_HPP__ + +#include "common/bitarray.hpp" +#include "common/bitmatrix.hpp" +#include "common/counted.hpp" +#include "errorhandler.hpp" +#include "luminance_source.hpp" + +#define ONED_ENABLE_LINE_BINARIZER + +namespace zxing { + +// typedef unsigned char uint8_t; + +struct BINARIZER_BLOCK { + int sum; + int min; + int max; + int threshold; + // int average; +}; + +#ifdef ONED_ENABLE_LINE_BINARIZER +struct DecodeTipInfo { + int class_id; +}; +#endif + +class Binarizer : public Counted { +private: + Ref source_; + bool histogramBinarized; + bool usingHistogram; + +public: + explicit Binarizer(Ref source); + virtual ~Binarizer(); + + // Added for store binarized result + + int dataWidth; + int dataHeight; + int width; + int height; + + // Store dynamicalli choice of which matrix is currently used + Ref matrix_; + + // Restore 0 degree result + Ref matrix0_; + + Ref matrixInverted_; + + bool isRotateSupported() const { return false; } + + // rotate counter clockwise 45 & 90 degree from binarized cache + int rotateCounterClockwise(); + int rotateCounterClockwise45(); + + virtual Ref getBlackMatrix(ErrorHandler& err_handler); + virtual Ref getInvertedMatrix(ErrorHandler& err_handler); + virtual Ref getBlackRow(int y, Ref row, ErrorHandler& err_handler); + + Ref getLuminanceSource() const; + // virtual Ref createBinarizer(Ref source) = 0; + virtual Ref createBinarizer(Ref source) { + return Ref(new Binarizer(source)); + }; + + int getWidth() const; + int getHeight() const; + + ArrayRef getBlockArray(int size); +}; + +} // namespace zxing +#endif // __ZXING_BINARIZER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/binarybitmap.cpp b/modules/wechat_qrcode/src/zxing/binarybitmap.cpp new file mode 100644 index 0000000..28352ca --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/binarybitmap.cpp @@ -0,0 +1,66 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../precomp.hpp" +#include "binarybitmap.hpp" + +using zxing::BinaryBitmap; +using zxing::BitArray; +using zxing::BitMatrix; +using zxing::ErrorHandler; +using zxing::LuminanceSource; +using zxing::Ref; + +// VC++ +using zxing::Binarizer; + +BinaryBitmap::BinaryBitmap(Ref binarizer) : binarizer_(binarizer) {} + +BinaryBitmap::~BinaryBitmap() {} + +Ref BinaryBitmap::getBlackRow(int y, Ref row, ErrorHandler& err_handler) { + Ref bitary = binarizer_->getBlackRow(y, row, err_handler); + if (err_handler.ErrCode()) return Ref(); + return bitary; +} + +Ref BinaryBitmap::getBlackMatrix(ErrorHandler& err_handler) { + Ref bitmtx = binarizer_->getBlackMatrix(err_handler); + if (err_handler.ErrCode()) return Ref(); + return bitmtx; +} + +Ref BinaryBitmap::getInvertedMatrix(ErrorHandler& err_handler) { + Ref bitmtx = binarizer_->getInvertedMatrix(err_handler); + if (err_handler.ErrCode()) return Ref(); + return bitmtx; +} + +int BinaryBitmap::getWidth() const { return binarizer_->getWidth(); } + +int BinaryBitmap::getHeight() const { return binarizer_->getHeight(); } + +Ref BinaryBitmap::getLuminanceSource() const { + return binarizer_->getLuminanceSource(); +} + +bool BinaryBitmap::isCropSupported() const { return getLuminanceSource()->isCropSupported(); } + +Ref BinaryBitmap::crop(int left, int top, int width, int height, + ErrorHandler& err_handler) { + return Ref(new BinaryBitmap(binarizer_->createBinarizer( + getLuminanceSource()->crop(left, top, width, height, err_handler)))); +} + +bool BinaryBitmap::isRotateSupported() const { return binarizer_->isRotateSupported(); } + +Ref BinaryBitmap::rotateCounterClockwise() { + binarizer_->rotateCounterClockwise(); + return Ref(new BinaryBitmap(binarizer_)); +} diff --git a/modules/wechat_qrcode/src/zxing/binarybitmap.hpp b/modules/wechat_qrcode/src/zxing/binarybitmap.hpp new file mode 100644 index 0000000..d483fcd --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/binarybitmap.hpp @@ -0,0 +1,53 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_BINARYBITMAP_HPP__ +#define __ZXING_BINARYBITMAP_HPP__ + +#include "binarizer.hpp" +#include "common/bitarray.hpp" +#include "common/bitmatrix.hpp" +#include "common/counted.hpp" +#include "common/unicomblock.hpp" +#include "errorhandler.hpp" + +namespace zxing { + +class BinaryBitmap : public Counted { +private: + Ref binarizer_; + +public: + explicit BinaryBitmap(Ref binarizer); + virtual ~BinaryBitmap(); + + Ref getBlackRow(int y, Ref row, ErrorHandler& err_handler); + Ref getBlackMatrix(ErrorHandler& err_handler); + Ref getInvertedMatrix(ErrorHandler& err_handler); + + Ref getLuminanceSource() const; + Ref m_poUnicomBlock; + + int getWidth() const; + int getHeight() const; + + bool isRotateSupported() const; + Ref rotateCounterClockwise(); + + bool isCropSupported() const; + Ref crop(int left, int top, int width, int height, ErrorHandler& err_handler); + + bool isHistogramBinarized() const; + bool ifUseHistogramBinarize() const; +}; + +} // namespace zxing + +#endif // __ZXING_BINARYBITMAP_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/array.hpp b/modules/wechat_qrcode/src/zxing/common/array.hpp new file mode 100644 index 0000000..9187937 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/array.hpp @@ -0,0 +1,113 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_ARRAY_HPP__ +#define __ZXING_COMMON_ARRAY_HPP__ + +#include "counted.hpp" + +namespace zxing { + +template +class Array : public Counted { +protected: +public: + std::vector values_; + Array() {} + explicit Array(int n) : Counted(), values_(n, T()) {} + Array(T const *ts, int n) : Counted(), values_(ts, ts + n) {} + Array(T const *ts, T const *te) : Counted(), values_(ts, te) {} + Array(T v, int n) : Counted(), values_(n, v) {} + explicit Array(std::vector &v) : Counted(), values_(v) {} + Array(Array &other) : Counted(), values_(other.values_) {} + explicit Array(Array *other) : Counted(), values_(other->values_) {} + virtual ~Array() {} + Array &operator=(const Array &other) { + values_ = other.values_; + return *this; + } + Array &operator=(const std::vector &array) { + values_ = array; + return *this; + } + T const &operator[](int i) const { return values_[i]; } + T &operator[](int i) { return values_[i]; } + int size() const { return values_.size(); } + bool empty() const { return values_.size() == 0; } + std::vector const &values() const { return values_; } + std::vector &values() { return values_; } + + T *data() { + // return values_.data(); + return &values_[0]; + } + void append(T value) { values_.push_back(value); } +}; + +template +class ArrayRef : public Counted { +private: +public: + Array *array_; + ArrayRef() : array_(0) {} + explicit ArrayRef(int n) : array_(0) { reset(new Array(n)); } + ArrayRef(T *ts, int n) : array_(0) { reset(new Array(ts, n)); } + explicit ArrayRef(Array *a) : array_(0) { reset(a); } + ArrayRef(const ArrayRef &other) : Counted(), array_(0) { reset(other.array_); } + + ~ArrayRef() { + if (array_) { + array_->release(); + } + array_ = 0; + } + + T const &operator[](int i) const { return (*array_)[i]; } + + T &operator[](int i) { return (*array_)[i]; } + + void reset(Array *a) { + if (a) { + a->retain(); + } + if (array_) { + array_->release(); + } + array_ = a; + } + void reset(const ArrayRef &other) { reset(other.array_); } + ArrayRef &operator=(const ArrayRef &other) { + reset(other); + return *this; + } + ArrayRef &operator=(Array *a) { + reset(a); + return *this; + } + + Array &operator*() const { return *array_; } + + Array *operator->() const { return array_; } + + operator bool() const { return array_ != 0; } + bool operator!() const { return array_ == 0; } + + T *data() { return array_->data(); } + + void clear() { + T *ptr = array_->data(); + memset(ptr, 0, array_->size()); + } + void append(T value) { array_->append(value); } +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_ARRAY_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/binarizer/adaptive_threshold_mean_binarizer.cpp b/modules/wechat_qrcode/src/zxing/common/binarizer/adaptive_threshold_mean_binarizer.cpp new file mode 100644 index 0000000..38a79b3 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/binarizer/adaptive_threshold_mean_binarizer.cpp @@ -0,0 +1,99 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "../../../precomp.hpp" +#include "adaptive_threshold_mean_binarizer.hpp" +using zxing::AdaptiveThresholdMeanBinarizer; + +namespace { +const int BLOCK_SIZE = 25; +const int Bias = 10; +} // namespace + +AdaptiveThresholdMeanBinarizer::AdaptiveThresholdMeanBinarizer(Ref source) + : GlobalHistogramBinarizer(source) {} + +AdaptiveThresholdMeanBinarizer::~AdaptiveThresholdMeanBinarizer() {} + +Ref AdaptiveThresholdMeanBinarizer::createBinarizer(Ref source) { + return Ref(new AdaptiveThresholdMeanBinarizer(source)); +} + +Ref AdaptiveThresholdMeanBinarizer::getBlackRow(int y, Ref row, + ErrorHandler& err_handler) { + // First call binarize image in child class to get matrix0_ and binCache + if (!matrix0_) { + binarizeImage(err_handler); + if (err_handler.ErrCode()) return Ref(); + } + + // Call parent getBlackMatrix to get current matrix + return Binarizer::getBlackRow(y, row, err_handler); +} + +Ref AdaptiveThresholdMeanBinarizer::getBlackMatrix(ErrorHandler& err_handler) { + // First call binarize image in child class to get matrix0_ and binCache + if (!matrix0_) { + binarizeImage(err_handler); + if (err_handler.ErrCode()) return Ref(); + } + return Binarizer::getBlackMatrix(err_handler); +} + +int AdaptiveThresholdMeanBinarizer::binarizeImage(ErrorHandler& err_handler) { + if (width >= BLOCK_SIZE && height >= BLOCK_SIZE) { + LuminanceSource& source = *getLuminanceSource(); + Ref matrix(new BitMatrix(width, height, err_handler)); + if (err_handler.ErrCode()) return -1; + auto src = (unsigned char*)source.getMatrix()->data(); + auto dst = matrix->getPtr(); + cv::Mat mDst; + mDst = cv::Mat::zeros(cv::Size(width, height), CV_8UC1); + TransBufferToMat(src, mDst, width, height); + cv::Mat result; + int bs = width / 10; + bs = bs + bs % 2 - 1; + if (!(bs % 2 == 1 && bs > 1)) return -1; + cv::adaptiveThreshold(mDst, result, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, + bs, Bias); + TransMatToBuffer(result, dst, width, height); + if (err_handler.ErrCode()) return -1; + matrix0_ = matrix; + } else { + matrix0_ = GlobalHistogramBinarizer::getBlackMatrix(err_handler); + if (err_handler.ErrCode()) return 1; + } + return 0; +} + +int AdaptiveThresholdMeanBinarizer::TransBufferToMat(unsigned char* pBuffer, cv::Mat& mDst, + int nWidth, int nHeight) { + for (int j = 0; j < nHeight; ++j) { + unsigned char* data = mDst.ptr(j); + unsigned char* pSubBuffer = pBuffer + (nHeight - 1 - j) * nWidth; + memcpy(data, pSubBuffer, nWidth); + } + return 0; +} + +int AdaptiveThresholdMeanBinarizer::TransMatToBuffer(cv::Mat mSrc, unsigned char* ppBuffer, + int& nWidth, int& nHeight) { + nWidth = mSrc.cols; + // nWidth = ((nWidth + 3) / 4) * 4; + nHeight = mSrc.rows; + for (int j = 0; j < nHeight; ++j) { + unsigned char* pdi = ppBuffer + j * nWidth; + for (int z = 0; z < nWidth; ++z) { + int nj = nHeight - j - 1; + int value = *(uchar*)(mSrc.ptr(nj) + z); + if (value > 120) + pdi[z] = 0; + else + pdi[z] = 1; + } + } + return 0; +} \ No newline at end of file diff --git a/modules/wechat_qrcode/src/zxing/common/binarizer/adaptive_threshold_mean_binarizer.hpp b/modules/wechat_qrcode/src/zxing/common/binarizer/adaptive_threshold_mean_binarizer.hpp new file mode 100644 index 0000000..bd51dde --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/binarizer/adaptive_threshold_mean_binarizer.hpp @@ -0,0 +1,38 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __ZXING_COMMON_ADAPTIVE_THRESHOLD_MEAN_BINARIZER_HPP__ +#define __ZXING_COMMON_ADAPTIVE_THRESHOLD_MEAN_BINARIZER_HPP__ +#include +#include +#include "../../binarizer.hpp" +#include "../../errorhandler.hpp" +#include "../bitarray.hpp" +#include "../bitmatrix.hpp" +#include "../bytematrix.hpp" +#include "global_histogram_binarizer.hpp" + + +namespace zxing { + +class AdaptiveThresholdMeanBinarizer : public GlobalHistogramBinarizer { +public: + explicit AdaptiveThresholdMeanBinarizer(Ref source); + virtual ~AdaptiveThresholdMeanBinarizer(); + + virtual Ref getBlackMatrix(ErrorHandler& err_handler) override; + virtual Ref getBlackRow(int y, Ref row, ErrorHandler& err_handler) override; + Ref createBinarizer(Ref source) override; + +private: + int binarizeImage(ErrorHandler& err_handler); + int TransBufferToMat(unsigned char* pBuffer, cv::Mat& mDst, int nWidth, int nHeight); + int TransMatToBuffer(cv::Mat mSrc, unsigned char* ppBuffer, int& nWidth, int& nHeight); +}; + +} // namespace zxing +#endif // __ZXING_COMMON_ADAPTIVE_THRESHOLD_MEAN_BINARIZER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/binarizer/fast_window_binarizer.cpp b/modules/wechat_qrcode/src/zxing/common/binarizer/fast_window_binarizer.cpp new file mode 100644 index 0000000..b0d9f65 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/binarizer/fast_window_binarizer.cpp @@ -0,0 +1,285 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "fast_window_binarizer.hpp" +using zxing::FastWindowBinarizer; + + +namespace { +const int BLOCK_SIZE = 6; +// const int BLOCK_SIZE = 8; // not as good as BLOCK_SIZE = 6 +const float WINDOW_FRACTION = 0.13f; + +static int min(int a, int b) { return a < b ? a : b; } + +static int max(int a, int b) { return a > b ? a : b; } + +} // namespace + +FastWindowBinarizer::FastWindowBinarizer(Ref source) + : GlobalHistogramBinarizer(source), matrix_(NULL), cached_row_(NULL) { + width = source->getWidth(); + height = source->getHeight(); + int aw = width / BLOCK_SIZE; + int ah = height / BLOCK_SIZE; + + int ah2 = ah; + int ow2 = aw + 1; + + _luminancesInt = new int[width * height]; + _blockTotals = new int[ah * aw]; + _totals = new int[(ah + 1) * (aw + 1)]; + _rowTotals = new int[ah2 * ow2]; + + _internal = new unsigned int[(height + 1) * (width + 1)]; +} + +FastWindowBinarizer::~FastWindowBinarizer() { + delete[] _totals; + delete[] _blockTotals; + delete[] _luminancesInt; + delete[] _rowTotals; + + delete[] _internal; +} + +Ref FastWindowBinarizer::createBinarizer(Ref source) { + return Ref(new FastWindowBinarizer(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. + */ +Ref FastWindowBinarizer::getBlackMatrix(ErrorHandler& err_handler) { + if (!matrix0_) { + binarizeImage1(err_handler); + if (err_handler.ErrCode()) return Ref(); + } + + return Binarizer::getBlackMatrix(err_handler); +} + +/** + * Calculate black row from BitMatrix + * If BitMatrix has been calculated then just get the row + * If BitMatrix has not been calculated then call getBlackMatrix first + */ + +Ref FastWindowBinarizer::getBlackRow(int y, Ref row, + ErrorHandler& err_handler) { + if (!matrix0_) { + binarizeImage1(err_handler); + if (err_handler.ErrCode()) return Ref(); + } + // Call parent getBlackMatrix to get current matrix + return Binarizer::getBlackRow(y, row, err_handler); +} + +void FastWindowBinarizer::calcBlockTotals(int* luminancesInt, int* output, int aw, int ah) { + for (int by = 0; by < ah; by++) { + int ey = (by + 1) * BLOCK_SIZE; + for (int bx = 0; bx < aw; bx++) { + int t = 0; + for (int y = by * BLOCK_SIZE; y < ey; y++) { + int offset = y * width + bx * BLOCK_SIZE; + int ex = offset + BLOCK_SIZE; + for (; offset < ex; offset++) { + // int v = luminancesInt[offset] & 0xff; + t += luminancesInt[offset]; + } + } + output[by * aw + bx] = t; + } + } +} + +void FastWindowBinarizer::cumulative(int* data, int* output, int _width, int _height) { + int ah = _height; + int aw = _width; + int ow = _width + 1; + // int[][] totals = new int[ah + 1][aw + 1]; + // int* rowTotals = new int[ah*ow]; + + for (int y = 0; y < ah; y++) { + int* row = _rowTotals + (y * ow); + int* rowdata = data + (y * aw); + int t = 0; + row[0] = t; + + for (int x = 0; x < aw; x++) { + t += rowdata[x]; + row[x + 1] = t; + } + } + + for (int x = 0; x <= aw; x++) { + output[x] = 0; // First row + int t = 0; + + for (int y = 0; y < ah; y++) { + t += _rowTotals[y * ow + x]; + output[(y + 1) * ow + x] = t; + } + } +} + +void FastWindowBinarizer::fastIntegral(const unsigned char* inputMatrix, + unsigned int* outputMatrix) { + // memset(outputMatrix,0,sizeof(int)*(height+1)*(width+1)); + // unsigned int *columnSum = new unsigned int[width]; // sum of each column + // calculate integral of the first line + outputMatrix[0] = outputMatrix[width + 1] = 0; + for (int i = 0; i < width; i++) { + // columnSum[i]=inputMatrix[i]; + outputMatrix[i + 1] = 0; + outputMatrix[width + 1 + i + 1] = outputMatrix[width + 1 + i] + inputMatrix[i]; + } + for (int i = 1; i < height; i++) { + const unsigned char* psi = inputMatrix + i * width; + unsigned int* pdi = outputMatrix + (i + 1) * (width + 1); + // first column of each line + pdi[0] = 0; + pdi[1] = psi[0]; + int row_sum = psi[0]; + // other columns + for (int j = 1; j < width; j++) { + row_sum += psi[j]; + pdi[j + 1] = pdi[j + 1 - width - 1] + row_sum; + } + } + return; +} + +int FastWindowBinarizer::binarizeImage1(ErrorHandler& err_handler) { + LuminanceSource& source = *getLuminanceSource(); + Ref matrix(new BitMatrix(width, height, err_handler)); + if (err_handler.ErrCode()) return -1; + + ArrayRef localLuminances = source.getMatrix(); + + unsigned char* src = (unsigned char*)localLuminances->data(); + unsigned char* dst = matrix->getPtr(); + fastWindow(src, dst, err_handler); + if (err_handler.ErrCode()) return -1; + + matrix0_ = matrix; + return 0; +} + +void FastWindowBinarizer::fastWindow(const unsigned char* src, unsigned char* dst, + ErrorHandler& err_handler) { + int r = (int)(min(width, height) * WINDOW_FRACTION / BLOCK_SIZE / 2 + 1); + const int NEWH_BLOCK_SIZE = BLOCK_SIZE * r; + if (height < NEWH_BLOCK_SIZE || width < NEWH_BLOCK_SIZE) { + matrix_ = GlobalHistogramBinarizer::getBlackMatrix(err_handler); + return; + } + const unsigned char* _img = src; + fastIntegral(_img, _internal); + int aw = width / BLOCK_SIZE; + int ah = height / BLOCK_SIZE; + memset(dst, 0, sizeof(char) * height * width); + for (int ai = 0; ai < ah; ai++) { + int top = max(0, ((ai - r + 1) * BLOCK_SIZE)); + int bottom = min(height, (ai + r) * BLOCK_SIZE); + unsigned int* pt = _internal + top * (width + 1); + unsigned int* pb = _internal + bottom * (width + 1); + for (int aj = 0; aj < aw; aj++) { + int left = max(0, (aj - r + 1) * BLOCK_SIZE); + int right = min(width, (aj + r) * BLOCK_SIZE); + unsigned int block = pb[right] + pt[left] - pt[right] - pb[left]; + int pixels = (bottom - top) * (right - left); + int avg = (int)block / pixels; + for (int bi = ai * BLOCK_SIZE; bi < height && bi < (ai + 1) * BLOCK_SIZE; bi++) { + const unsigned char* psi = src + bi * width; + unsigned char* pdi = dst + bi * width; + for (int bj = aj * BLOCK_SIZE; bj < width && bj < (aj + 1) * BLOCK_SIZE; bj++) { + if ((int)psi[bj] < avg) + pdi[bj] = 1; + else + pdi[bj] = 0; + } + } + } + } + // delete [] _internal; + return; +} + +int FastWindowBinarizer::binarizeImage0(ErrorHandler& err_handler) { + // if (matrix_) { + // return matrix_; + //} + + LuminanceSource& source = *getLuminanceSource(); + if (width >= BLOCK_SIZE && height >= BLOCK_SIZE) { + int r = (int)(min(width, height) * WINDOW_FRACTION / BLOCK_SIZE / 2 + 1); + + int aw = width / BLOCK_SIZE; + int ah = height / BLOCK_SIZE; + int ow = aw + 1; + + ArrayRef _luminances = source.getMatrix(); + + // Get luminances for int value first + for (int i = 0; i < width * height; i++) { + _luminancesInt[i] = _luminances[i] & 0xff; + } + + calcBlockTotals(_luminancesInt, _blockTotals, aw, ah); + + cumulative(_blockTotals, _totals, aw, ah); + + Ref newMatrix(new BitMatrix(width, height, err_handler)); + if (err_handler.ErrCode()) return -1; + unsigned char* newimg = newMatrix->getPtr(); + for (int by = 0; by < ah; by++) { + int top = max(0, by - r + 1); + int bottom = min(ah, by + r); + + for (int bx = 0; bx < aw; bx++) { + int left = max(0, bx - r + 1); + int right = min(aw, bx + r); + + int block = _totals[bottom * ow + right] + _totals[top * ow + left] - + _totals[top * ow + right] - _totals[bottom * ow + left]; + + int pixels = (bottom - top) * (right - left) * BLOCK_SIZE * BLOCK_SIZE; + int avg = block / pixels; + + for (int y = by * BLOCK_SIZE; y < (by + 1) * BLOCK_SIZE; y++) { + // int offset = y*width; + int* plumint = _luminancesInt + y * width; + unsigned char* pn = newimg + y * width; + for (int x = bx * BLOCK_SIZE; x < (bx + 1) * BLOCK_SIZE; x++) { + // int pixel = luminances[y*width + x] & 0xff; + // if(plumint[x] < avg) + // newMatrix->set(x, y); + if (plumint[x] < avg) + pn[x] = 1; + else + pn[x] = 0; + } + } + } + } + // delete[] data; + matrix_ = newMatrix; + } else { + // If the image is too small, fall back to the global histogram + // approach. + matrix_ = GlobalHistogramBinarizer::getBlackMatrix(err_handler); + } + + return 0; +} diff --git a/modules/wechat_qrcode/src/zxing/common/binarizer/fast_window_binarizer.hpp b/modules/wechat_qrcode/src/zxing/common/binarizer/fast_window_binarizer.hpp new file mode 100644 index 0000000..a18c186 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/binarizer/fast_window_binarizer.hpp @@ -0,0 +1,56 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_DETECTOR_RESULT_HPP__ +#define __ZXING_COMMON_DETECTOR_RESULT_HPP__ + +#include "../../binarizer.hpp" +#include "../../errorhandler.hpp" +#include "../bitarray.hpp" +#include "../bitmatrix.hpp" +#include "global_histogram_binarizer.hpp" + +#include + +namespace zxing { + +class FastWindowBinarizer : public GlobalHistogramBinarizer { +private: + Ref matrix_; + Ref cached_row_; + + int* _luminancesInt; + int* _blockTotals; + int* _totals; + int* _rowTotals; + + unsigned int* _internal; + +public: + explicit FastWindowBinarizer(Ref source); + virtual ~FastWindowBinarizer(); + + virtual Ref getBlackMatrix(ErrorHandler& err_handler) override; + virtual Ref getBlackRow(int y, Ref row, ErrorHandler& err_handler) override; + + Ref createBinarizer(Ref source) override; + +private: + void calcBlockTotals(int* luminancesInt, int* output, int aw, int ah); + void cumulative(int* data, int* output, int _width, int _height); + int binarizeImage0(ErrorHandler& err_handler); + void fastIntegral(const unsigned char* inputMatrix, unsigned int* outputMatrix); + int binarizeImage1(ErrorHandler& err_handler); + void fastWindow(const unsigned char* src, unsigned char* dst, ErrorHandler& err_handler); +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_DETECTOR_RESULT_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/binarizer/global_histogram_binarizer.cpp b/modules/wechat_qrcode/src/zxing/common/binarizer/global_histogram_binarizer.cpp new file mode 100644 index 0000000..0f1aa05 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/binarizer/global_histogram_binarizer.cpp @@ -0,0 +1,308 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "global_histogram_binarizer.hpp" +using zxing::GlobalHistogramBinarizer; + +namespace { +const int LUMINANCE_BITS = 5; +const int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; +const int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; +const ArrayRef EMPTY(0); +} // namespace + +GlobalHistogramBinarizer::GlobalHistogramBinarizer(Ref source) + : Binarizer(source), luminances(EMPTY), buckets(LUMINANCE_BUCKETS) { + filtered = false; +} + +GlobalHistogramBinarizer::~GlobalHistogramBinarizer() {} + +void GlobalHistogramBinarizer::initArrays(int luminanceSize) { + if (luminances->size() < luminanceSize) { + luminances = ArrayRef(luminanceSize); + } + for (int x = 0; x < LUMINANCE_BUCKETS; x++) { + buckets[x] = 0; + } +} + +// Applies simple sharpening to the row data to improve performance of the 1D +// readers. +Ref GlobalHistogramBinarizer::getBlackRow(int y, Ref row, + ErrorHandler& err_handler) { + // First call binarize image in child class to get matrix0_ and binCache + if (!matrix0_) { + binarizeImage0(err_handler); + if (err_handler.ErrCode()) return Ref(); + } + // Call parent getBlackMatrix to get current matrix + return Binarizer::getBlackRow(y, row, err_handler); +} + +// Does not sharpen the data, as this call is intended to only be used by 2D +// readers. +Ref GlobalHistogramBinarizer::getBlackMatrix(ErrorHandler& err_handler) { + binarizeImage0(err_handler); + if (err_handler.ErrCode()) return Ref(); + // First call binarize image in child class to get matrix0_ and binCache + // Call parent getBlackMatrix to get current matrix + return Binarizer::getBlackMatrix(err_handler); +} + +using namespace std; + +int GlobalHistogramBinarizer::estimateBlackPoint(ArrayRef const& _buckets, + ErrorHandler& err_handler) { + // Find tallest peak in histogram + int numBuckets = _buckets->size(); + 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 second-tallest peak -- well, another peak that is tall and not + // so close to the first one + 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; + } + + // Kind of arbitrary; if the two peaks are very close, then we figure there + // is so little dynamic range in the image, that discriminating black and + // white is too error-prone. Decoding the image/line is either pointless, or + // may in some cases lead to a false positive for 1D formats, which are + // relatively lenient. We arbitrarily say "close" is "<= 1/16 of the total + // histogram buckets apart" std::cerr << "! " << secondPeak << " " << + // firstPeak << " " << numBuckets << std::endl; + if (secondPeak - firstPeak <= numBuckets >> 4) { + err_handler = NotFoundErrorHandler("NotFound GlobalHistogramBinarizer"); + return -1; + } + + // 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; + // Favor a "valley" that is not too close to either peak -- especially + // not the black peak -- and that has a low value of course + int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]); + if (score > bestValleyScore) { + bestValley = x; + bestValleyScore = score; + } + } + + // std::cerr << "bps " << (bestValley << LUMINANCE_SHIFT) << std::endl; + return bestValley << LUMINANCE_SHIFT; +} + +// codes from sagazhou, only works well on one dataset +int GlobalHistogramBinarizer::estimateBlackPoint2(ArrayRef const& _buckets) { + int midValue = LUMINANCE_BUCKETS / 2 + 1; + // Find tallest and lowest peaks in histogram + // const int numBuckets = buckets->size(); + int maxPointArray[LUMINANCE_BUCKETS] = {0}; + int maxCrusor = 0; + int maxValue = 0, maxIndex = 0; + int minPointArray[LUMINANCE_BUCKETS] = {0}; + int minCrusor = 0; + + for (int i = 2; i < LUMINANCE_BUCKETS - 3; i++) { + if (_buckets[i] < _buckets[i + 1] && _buckets[i] < _buckets[i + 2] && + _buckets[i] < _buckets[i - 1] && _buckets[i] < _buckets[i - 2]) { + minPointArray[minCrusor++] = i; + } else if (_buckets[i] > _buckets[i + 1] && _buckets[i] > _buckets[i + 2] && + _buckets[i] > _buckets[i - 1] && _buckets[i] > _buckets[i - 2]) { + maxPointArray[maxCrusor++] = i; + if (_buckets[i] > maxValue) { + maxValue = _buckets[i]; + maxIndex = i; + } + } + } + bool bSlantBlack = true; + // most pixels are black + for (int i = 0; i < maxCrusor; ++i) { + if (maxPointArray[i] > midValue) { + bSlantBlack = false; + break; + } + } + + bool bSlantWhite = true; + // most pixels are white + for (int i = 0; i < maxCrusor; ++i) { + if (maxPointArray[i] < midValue) { + bSlantWhite = false; + break; + } + } + + if (bSlantBlack) { + int start = maxIndex + 30; + int end = midValue; + + if (minCrusor == 0) // unimodal + { + return 255; + } else { + int mostLeftIndex = 0; + bool bFind = false; + + for (int i = 0; i < minCrusor; ++i) // wave motion + { + if (minPointArray[i] > start && minPointArray[i] < end) { + mostLeftIndex = minPointArray[i]; + bFind = true; + break; + } + } + + if (bFind) { + return mostLeftIndex; + } else { + return 255; + } + } + } + + if (bSlantWhite) { + int start = midValue; + int end = maxIndex - 30; + + if (minCrusor == 0) // unimodal + { + return 0; + } else { + int mostRightIndex = 0; + bool bFind = false; + + for (int i = 0; i < minCrusor; ++i) // wave motion + { + if (minPointArray[i] > start && minPointArray[i] < end) { + mostRightIndex = i; + bFind = true; + } + } + + if (bFind) { + return mostRightIndex; + } else { + return 0; + } + } + } + + // balanced distribution + if (maxIndex < midValue) { + // the minest min value + if (minCrusor == 0) { + return 255; // all black + } else { + int start = maxIndex + 30; + int end = 253; + + for (int i = 0; i < minCrusor; ++i) // wave motion + { + if (minPointArray[i] > start && minPointArray[i] < end) { + return minPointArray[i]; + } + } + } + } else { + // maxest min value + if (minCrusor == 0) { + return 0; // white + } else { + int start = 0; + int end = maxIndex - 30; + int mostRightIndex = 0; + + for (int i = 0; i < minCrusor; ++i) // wave motion + { + if (minPointArray[i] > start && minPointArray[i] < end) { + mostRightIndex = minPointArray[i]; + } + } + + return mostRightIndex; + } + } + return 0; +} + +int GlobalHistogramBinarizer::binarizeImage0(ErrorHandler& err_handler) { + LuminanceSource& source = *getLuminanceSource(); + Ref matrix(new BitMatrix(width, height, err_handler)); + if (err_handler.ErrCode()) return -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); + ArrayRef localBuckets = buckets; + + for (int y = 1; y < 5; y++) { + int row = height * y / 5; + ArrayRef localLuminances = source.getRow(row, luminances, err_handler); + if (err_handler.ErrCode()) return -1; + int right = (width << 2) / 5; + for (int x = width / 5; x < right; x++) { + int pixel = localLuminances[x] & 0xff; + localBuckets[pixel >> LUMINANCE_SHIFT]++; + } + } + + + int blackPoint = estimateBlackPoint(localBuckets, err_handler); + if (err_handler.ErrCode()) return -1; + + ArrayRef 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); + } + } + } + + matrix0_ = matrix; + + return 0; +} + +Ref GlobalHistogramBinarizer::createBinarizer(Ref source) { + return Ref(new GlobalHistogramBinarizer(source)); +} diff --git a/modules/wechat_qrcode/src/zxing/common/binarizer/global_histogram_binarizer.hpp b/modules/wechat_qrcode/src/zxing/common/binarizer/global_histogram_binarizer.hpp new file mode 100644 index 0000000..f61b3cf --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/binarizer/global_histogram_binarizer.hpp @@ -0,0 +1,55 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_GLOBAL_HISTOGRAM_BINARIZER_HPP__ +#define __ZXING_COMMON_GLOBAL_HISTOGRAM_BINARIZER_HPP__ + +#include "../../binarizer.hpp" +#include "../../errorhandler.hpp" +#include "../array.hpp" +#include "../bitarray.hpp" +#include "../bitmatrix.hpp" +#include "../bytematrix.hpp" + +using zxing::ArrayRef; +using zxing::Binarizer; +using zxing::BitArray; +using zxing::BitMatrix; +using zxing::ByteMatrix; +using zxing::ErrorHandler; +using zxing::LuminanceSource; +using zxing::Ref; + +namespace zxing { + +class GlobalHistogramBinarizer : public Binarizer { +protected: + ArrayRef luminances; + ArrayRef buckets; + +public: + explicit GlobalHistogramBinarizer(Ref source); + virtual ~GlobalHistogramBinarizer(); + + virtual Ref getBlackRow(int y, Ref row, ErrorHandler &err_handler) override; + virtual Ref getBlackMatrix(ErrorHandler &err_handler) override; + int estimateBlackPoint(ArrayRef const &buckets, ErrorHandler &err_handler); + int estimateBlackPoint2(ArrayRef const &buckets); + Ref createBinarizer(Ref source) override; + +private: + int binarizeImage0(ErrorHandler &err_handler); + void initArrays(int luminanceSize); + bool filtered; +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_GLOBAL_HISTOGRAM_BINARIZER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/binarizer/hybrid_binarizer.cpp b/modules/wechat_qrcode/src/zxing/common/binarizer/hybrid_binarizer.cpp new file mode 100644 index 0000000..6787b24 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/binarizer/hybrid_binarizer.cpp @@ -0,0 +1,424 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "hybrid_binarizer.hpp" + +using zxing::HybridBinarizer; +using zxing::BINARIZER_BLOCK; + +// This class uses 5*5 blocks to compute local luminance, where each block is +// 8*8 pixels So this is the smallest dimension in each axis we can accept. +namespace { +const int BLOCK_SIZE_POWER = 3; +const int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00 +const int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; // ...0011...11 +const int MINIMUM_DIMENSION = BLOCK_SIZE * 5; +#ifdef USE_SET_INT +const int BITS_PER_BYTE = 8; +const int BITS_PER_WORD = BitMatrix::bitsPerWord; +#endif +} // namespace + +HybridBinarizer::HybridBinarizer(Ref source) : GlobalHistogramBinarizer(source) { + 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++; + } + + grayByte_ = source->getByteMatrix(); + + blocks_ = getBlockArray(subWidth * subHeight); + + subWidth_ = subWidth; + subHeight_ = subHeight; + + initBlocks(); + initBlockIntegral(); +} + +HybridBinarizer::~HybridBinarizer() { +} + +Ref HybridBinarizer::createBinarizer(Ref source) { + return Ref(new GlobalHistogramBinarizer(source)); +} + +int HybridBinarizer::initBlockIntegral() { + blockIntegral_ = new Array(width * height); + + int* integral = blockIntegral_->data(); + + // unsigned char* therow = grayByte_->getByteRow(0); + + // first row only + int rs = 0; + + for (int j = 0; j < width; j++) { + integral[j] = 0; + } + + for (int i = 0; i < height; i++) { + integral[i * width] = 0; + } + + for (int j = 0; j < subWidth_; j++) { + rs += blocks_[j].threshold; + integral[width + j + 1] = rs; + } + + // remaining cells are sum above and to the left + int offset = width; + int offsetBlock = 0; + + for (int i = 1; i < subHeight_; ++i) { + // therow = grayByte_->getByteRow(i); + offsetBlock = i * subWidth_; + + rs = 0; + + offset += width; + + for (int j = 0; j < subWidth_; ++j) { + rs += blocks_[offsetBlock + j].threshold; + integral[offset + j + 1] = rs + integral[offset - width + j + 1]; + } + } + + return 1; +} + +/** + * 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. + */ +Ref HybridBinarizer::getBlackMatrix(ErrorHandler& err_handler) { + // First call binarize image in child class to get matrix0_ and binCache + if (!matrix0_) { + binarizeByBlock(err_handler); + if (err_handler.ErrCode()) return Ref(); + } + + // First call binarize image in child class to get matrix0_ and binCache + // Call parent getBlackMatrix to get current matrix + return Binarizer::getBlackMatrix(err_handler); +} + +#if 1 +/** + * Calculate black row from BitMatrix + * If BitMatrix has been calculated then just get the row + * If BitMatrix has not been calculated then call getBlackMatrix first + */ +Ref HybridBinarizer::getBlackRow(int y, Ref row, ErrorHandler& err_handler) { + // First call binarize image in child class to get matrix0_ and binCache + if (!matrix0_) { + binarizeByBlock(err_handler); + if (err_handler.ErrCode()) return Ref(); + } + + // Call parent getBlackMatrix to get current matrix + return Binarizer::getBlackRow(y, row, err_handler); +} +#endif + +namespace { +inline int cap(int value, int min, int max) { + return value < min ? min : value > max ? max : value; +} +} // namespace + + +// For each block in the image, calculates the average black point using a 5*5 +// 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.) + +#define THRES_BLOCKSIZE 2 + +// No use of level now +ArrayRef HybridBinarizer::getBlackPoints() { + int blackWidth, blackHeight; + + blackWidth = subWidth_; + blackHeight = subHeight_; + + ArrayRef blackPoints(blackWidth * blackHeight); + + int* blackArray = blackPoints->data(); + + int offset = 0; + for (int i = 0; i < blackHeight; i++) { + offset = i * blackWidth; + for (int j = 0; j < blackWidth; j++) { + blackArray[offset + j] = blocks_[offset + j].threshold; + } + } + + return blackPoints; +} + +// Original code 20140606 +void HybridBinarizer::calculateThresholdForBlock(Ref& _luminances, int subWidth, + int subHeight, int SIZE_POWER, + // ArrayRef &blackPoints, + Ref const& matrix, + ErrorHandler& err_handler) { + int block_size = 1 << SIZE_POWER; + + int maxYOffset = height - block_size; + int maxXOffset = width - block_size; + + int* blockIntegral = blockIntegral_->data(); + + int blockArea = ((2 * THRES_BLOCKSIZE + 1) * (2 * THRES_BLOCKSIZE + 1)); + + for (int y = 0; y < subHeight; y++) { + int yoffset = y << SIZE_POWER; + if (yoffset > maxYOffset) { + yoffset = maxYOffset; + } + for (int x = 0; x < subWidth; x++) { + int xoffset = x << SIZE_POWER; + if (xoffset > maxXOffset) { + xoffset = maxXOffset; + } + int left = cap(x, THRES_BLOCKSIZE, subWidth - THRES_BLOCKSIZE - 1); + int top = cap(y, THRES_BLOCKSIZE, subHeight - THRES_BLOCKSIZE - 1); + + int sum = 0; + // int sum2 = 0; + + int offset1 = (top - THRES_BLOCKSIZE) * (subWidth + 1) + left - THRES_BLOCKSIZE; + int offset2 = (top + THRES_BLOCKSIZE + 1) * (subWidth + 1) + left - THRES_BLOCKSIZE; + + int blocksize = THRES_BLOCKSIZE * 2 + 1; + + sum = blockIntegral[offset1] - blockIntegral[offset1 + blocksize] - + blockIntegral[offset2] + blockIntegral[offset2 + blocksize]; + + int average = sum / blockArea; + thresholdBlock(_luminances, xoffset, yoffset, average, matrix, err_handler); + if (err_handler.ErrCode()) return; + } + } +} + +#ifdef USE_SET_INT +void HybridBinarizer::thresholdFourBlocks(Ref& luminances, int xoffset, int yoffset, + int* thresholds, int stride, + Ref const& matrix) { + int setIntCircle = BITS_PER_WORD / BITS_PER_BYTE; + for (int y = 0; y < BLOCK_SIZE; y++) { + unsigned char* pTemp = luminances->getByteRow(yoffset + y); + pTemp = pTemp + xoffset; + unsigned int valueInt = 0; + int bitPosition = 0; + for (int k = 0; k < setIntCircle; k++) { + for (int x = 0; x < BLOCK_SIZE; x++) { + int pixel = *pTemp++; + if (pixel <= thresholds[k]) { + // bitPosition=(3-k)*8+x; + valueInt |= (unsigned int)1 << bitPosition; + } + bitPosition++; + } + } + matrix->setIntOneTime(xoffset, yoffset + y, valueInt); + } + return; +} +#endif + +// Applies a single threshold to a block of pixels +void HybridBinarizer::thresholdBlock(Ref& _luminances, int xoffset, int yoffset, + int threshold, Ref const& matrix, + ErrorHandler& err_handler) { + int rowBitsSize = matrix->getRowBitsSize(); + int rowSize = width; + + int rowBitStep = rowBitsSize - BLOCK_SIZE; + int rowStep = rowSize - BLOCK_SIZE; + + unsigned char* pTemp = _luminances->getByteRow(yoffset, err_handler); + if (err_handler.ErrCode()) return; + bool* bpTemp = matrix->getRowBoolPtr(yoffset); + + pTemp += xoffset; + bpTemp += xoffset; + + for (int y = 0; y < BLOCK_SIZE; y++) { + 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. + *bpTemp++ = (*pTemp++ <= threshold) ? true : false; + } + + pTemp += rowBitStep; + bpTemp += rowStep; + } +} + +void HybridBinarizer::thresholdIrregularBlock(Ref& _luminances, int xoffset, + int yoffset, int blockWidth, int blockHeight, + int threshold, Ref const& matrix, + ErrorHandler& err_handler) { + for (int y = 0; y < blockHeight; y++) { + unsigned char* pTemp = _luminances->getByteRow(yoffset + y, err_handler); + if (err_handler.ErrCode()) return; + pTemp = pTemp + xoffset; + for (int x = 0; x < blockWidth; x++) { + // comparison needs to be <= so that black == 0 pixels are black + // even if the threshold is 0. + int pixel = *pTemp++; + if (pixel <= threshold) { + matrix->set(xoffset + x, yoffset + y); + } + } + } +} + +namespace { + +inline int getBlackPointFromNeighbors(ArrayRef block, int subWidth, int x, int y) { + return (block[(y - 1) * subWidth + x].threshold + 2 * block[y * subWidth + x - 1].threshold + + block[(y - 1) * subWidth + x - 1].threshold) >> + 2; +} + +} // namespace + + +#define MIN_DYNAMIC_RANGE 24 + +// Calculates a single black point for each block of pixels and saves it away. +int HybridBinarizer::initBlocks() { + Ref& _luminances = grayByte_; + int subWidth = subWidth_; + int subHeight = subHeight_; + + unsigned char* bytes = _luminances->bytes; + + const int minDynamicRange = 24; + + for (int y = 0; y < subHeight; y++) { + int yoffset = y << BLOCK_SIZE_POWER; + int maxYOffset = height - BLOCK_SIZE; + if (yoffset > maxYOffset) yoffset = maxYOffset; + for (int x = 0; x < subWidth; x++) { + int xoffset = x << BLOCK_SIZE_POWER; + int maxXOffset = width - BLOCK_SIZE; + 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->bytes[offset + xx] & 0xFF; + int pixel = bytes[offset + xx]; + 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 > minDynamicRange) { + // 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 += 2) { + sum += bytes[offset + xx]; + sum += bytes[offset + xx + 1]; + } + } + } + } + + blocks_[y * subWidth + x].min = min; + blocks_[y * subWidth + x].max = max; + blocks_[y * subWidth + x].sum = sum; + blocks_[y * subWidth + x].threshold = + getBlockThreshold(x, y, subWidth, sum, min, max, minDynamicRange, BLOCK_SIZE_POWER); + } + } + + return 1; +} + +int HybridBinarizer::getBlockThreshold(int x, int y, int subWidth, int sum, int min, int max, + int minDynamicRange, int SIZE_POWER) { + // See + // http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 + + // The default estimate is the average of the values in the block. + int average = sum >> (SIZE_POWER * 2); + if (max - min <= minDynamicRange) { + // If variation within the block is low, assume this is a block withe + // 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 >> 1; + 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 amout of light + // background for which reasonable black point estimates were made. + // The bp estimated at the boundaries is used for the interior. + int bp = getBlackPointFromNeighbors(blocks_, subWidth, x, y); + // The (min= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION) { + Ref newMatrix(new BitMatrix(width, height, err_handler)); + if (err_handler.ErrCode()) return -1; + + calculateThresholdForBlock(grayByte_, subWidth_, subHeight_, BLOCK_SIZE_POWER, newMatrix, + err_handler); + if (err_handler.ErrCode()) return -1; + + matrix0_ = newMatrix; + + } else { + // If the image is too small, fall back to the global histogram + // approach. + matrix0_ = GlobalHistogramBinarizer::getBlackMatrix(err_handler); + if (err_handler.ErrCode()) return 1; + } + // return matrix0_; + return 1; +} diff --git a/modules/wechat_qrcode/src/zxing/common/binarizer/hybrid_binarizer.hpp b/modules/wechat_qrcode/src/zxing/common/binarizer/hybrid_binarizer.hpp new file mode 100644 index 0000000..758adc0 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/binarizer/hybrid_binarizer.hpp @@ -0,0 +1,83 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_HYBRID_BINARIZER_HPP__ +#define __ZXING_COMMON_HYBRID_BINARIZER_HPP__ + +#include "../../binarizer.hpp" +#include "../../errorhandler.hpp" +#include "../bitarray.hpp" +#include "../bitmatrix.hpp" +#include "../bytematrix.hpp" +#include "global_histogram_binarizer.hpp" + +#include + + +namespace zxing { + +class HybridBinarizer : public GlobalHistogramBinarizer { +private: + Ref grayByte_; + // ArrayRef integral_; + ArrayRef blockIntegral_; + ArrayRef blocks_; + + ArrayRef blackPoints_; + int level_; + + int subWidth_; + int subHeight_; + +public: + explicit HybridBinarizer(Ref source); + virtual ~HybridBinarizer(); + + virtual Ref getBlackMatrix(ErrorHandler& err_handler) override; + virtual Ref getBlackRow(int y, Ref row, ErrorHandler& err_handler) override; + + Ref createBinarizer(Ref source) override; + +private: + int initIntegral(); + int initBlockIntegral(); + int initBlocks(); + + // int calculateBlackPoints(); + ArrayRef getBlackPoints(); + int getBlockThreshold(int x, int y, int subWidth, int sum, int min, int max, + int minDynamicRange, int SIZE_POWER); + + + void calculateThresholdForBlock(Ref& luminances, int subWidth, int subHeight, + int SIZE_POWER, Ref const& matrix, + ErrorHandler& err_handler); + + + void thresholdBlock(Ref& luminances, int xoffset, int yoffset, int threshold, + Ref const& matrix, ErrorHandler& err_handler); + + void thresholdIrregularBlock(Ref& luminances, int xoffset, int yoffset, + int blockWidth, int blockHeight, int threshold, + Ref const& matrix, ErrorHandler& err_handler); + +#ifdef USE_SET_INT + void thresholdFourBlocks(Ref& luminances, int xoffset, int yoffset, int* thresholds, + int stride, Ref const& matrix); +#endif + + // Add for binarize image when call getBlackMatrix + // By Skylook + int binarizeByBlock(ErrorHandler& err_handler); +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_HYBRID_BINARIZER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/binarizer/simple_adaptive_binarizer.cpp b/modules/wechat_qrcode/src/zxing/common/binarizer/simple_adaptive_binarizer.cpp new file mode 100644 index 0000000..2364bc3 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/binarizer/simple_adaptive_binarizer.cpp @@ -0,0 +1,155 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "simple_adaptive_binarizer.hpp" + +using zxing::SimpleAdaptiveBinarizer; + + +SimpleAdaptiveBinarizer::SimpleAdaptiveBinarizer(Ref source) + : GlobalHistogramBinarizer(source) { + filtered = false; +} + +SimpleAdaptiveBinarizer::~SimpleAdaptiveBinarizer() {} + +// Applies simple sharpening to the row data to improve performance of the 1D +// readers. +Ref SimpleAdaptiveBinarizer::getBlackRow(int y, Ref row, + ErrorHandler &err_handler) { + // First call binarize image in child class to get matrix0_ and binCache + if (!matrix0_) { + binarizeImage0(err_handler); + if (err_handler.ErrCode()) return Ref(); + } + // Call parent getBlackMatrix to get current matrix + return Binarizer::getBlackRow(y, row, err_handler); +} + +// Does not sharpen the data, as this call is intended to only be used by 2D +// readers. +Ref SimpleAdaptiveBinarizer::getBlackMatrix(ErrorHandler &err_handler) { + // First call binarize image in child class to get matrix0_ and binCache + if (!matrix0_) { + binarizeImage0(err_handler); + if (err_handler.ErrCode()) return Ref(); + } + + // First call binarize image in child class to get matrix0_ and binCache + // Call parent getBlackMatrix to get current matrix + return Binarizer::getBlackMatrix(err_handler); +} + +using namespace std; + +int SimpleAdaptiveBinarizer::binarizeImage0(ErrorHandler &err_handler) { + LuminanceSource &source = *getLuminanceSource(); + + Ref matrix(new BitMatrix(width, height, err_handler)); + if (err_handler.ErrCode()) return -1; + + ArrayRef localLuminances = source.getMatrix(); + + unsigned char *src = (unsigned char *)localLuminances->data(); + unsigned char *dst = matrix->getPtr(); + + qrBinarize(src, dst); + + matrix0_ = matrix; + + return 0; +} + +/*A simplified adaptive thresholder. + This compares the current pixel value to the mean value of a (large) window + surrounding it.*/ +int SimpleAdaptiveBinarizer::qrBinarize(const unsigned char *src, unsigned char *dst) { + unsigned char *mask = dst; + + if (width > 0 && height > 0) { + unsigned *col_sums; + int logwindw; + int logwindh; + int windw; + int windh; + int y0offs; + int y1offs; + unsigned g; + int x; + int y; + /*We keep the window size fairly large to ensure it doesn't fit + completely inside the center of a finder pattern of a version 1 QR + code at full resolution.*/ + for (logwindw = 4; logwindw < 8 && (1 << logwindw) < ((width + 7) >> 3); logwindw++) + ; + for (logwindh = 4; logwindh < 8 && (1 << logwindh) < ((height + 7) >> 3); logwindh++) + ; + windw = 1 << logwindw; + windh = 1 << logwindh; + + int logwinds = (logwindw + logwindh); + + col_sums = (unsigned *)malloc(width * sizeof(*col_sums)); + /*Initialize sums down each column.*/ + for (x = 0; x < width; x++) { + g = src[x]; + col_sums[x] = (g << (logwindh - 1)) + g; + } + for (y = 1; y < (windh >> 1); y++) { + y1offs = min(y, height - 1) * width; + for (x = 0; x < width; x++) { + g = src[y1offs + x]; + col_sums[x] += g; + } + } + for (y = 0; y < height; y++) { + unsigned m; + int x0; + int x1; + /*Initialize the sum over the window.*/ + m = (col_sums[0] << (logwindw - 1)) + col_sums[0]; + for (x = 1; x < (windw >> 1); x++) { + x1 = min(x, width - 1); + m += col_sums[x1]; + } + + int offset = y * width; + + for (x = 0; x < width; x++) { + /*Perform the test against the threshold T = (m/n)-D, + where n=windw*windh and D=3.*/ + g = src[offset + x]; + mask[offset + x] = ((g + 3) << (logwinds) < m); + /*Update the window sum.*/ + if (x + 1 < width) { + x0 = max(0, x - (windw >> 1)); + x1 = min(x + (windw >> 1), width - 1); + m += col_sums[x1] - col_sums[x0]; + } + } + /*Update the column sums.*/ + if (y + 1 < height) { + y0offs = max(0, y - (windh >> 1)) * width; + y1offs = min(y + (windh >> 1), height - 1) * width; + for (x = 0; x < width; x++) { + col_sums[x] -= src[y0offs + x]; + col_sums[x] += src[y1offs + x]; + } + } + } + free(col_sums); + } + + return 1; +} + +Ref SimpleAdaptiveBinarizer::createBinarizer(Ref source) { + return Ref(new SimpleAdaptiveBinarizer(source)); +} diff --git a/modules/wechat_qrcode/src/zxing/common/binarizer/simple_adaptive_binarizer.hpp b/modules/wechat_qrcode/src/zxing/common/binarizer/simple_adaptive_binarizer.hpp new file mode 100644 index 0000000..956e87e --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/binarizer/simple_adaptive_binarizer.hpp @@ -0,0 +1,40 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_SIMPLEADAPTIVEBINARIZER_HPP__ +#define __ZXING_COMMON_SIMPLEADAPTIVEBINARIZER_HPP__ + +#include "../../binarizer.hpp" +#include "../array.hpp" +#include "../bitarray.hpp" +#include "../bitmatrix.hpp" +#include "global_histogram_binarizer.hpp" + + +namespace zxing { + +class SimpleAdaptiveBinarizer : public GlobalHistogramBinarizer { +public: + explicit SimpleAdaptiveBinarizer(Ref source); + virtual ~SimpleAdaptiveBinarizer(); + + virtual Ref getBlackRow(int y, Ref row, ErrorHandler &err_handler) override; + virtual Ref getBlackMatrix(ErrorHandler &err_handler) override; + Ref createBinarizer(Ref source) override; + +private: + int binarizeImage0(ErrorHandler &err_handler); + int qrBinarize(const unsigned char *src, unsigned char *dst); + bool filtered; +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_SIMPLEADAPTIVEBINARIZER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/bitarray.cpp b/modules/wechat_qrcode/src/zxing/common/bitarray.cpp new file mode 100644 index 0000000..768f625 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/bitarray.cpp @@ -0,0 +1,239 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "bitarray.hpp" + +using zxing::ArrayRef; +using zxing::BitArray; +using zxing::ErrorHandler; +using zxing::Ref; + +#if __WORDSIZE == 64 +// typedef long int int64_t; +#else +typedef long long int int64_t; +#endif + +BitArray::BitArray(int size_) : size(size_), bits(size_), nextSets(size_), nextUnSets(size_) {} + +void BitArray::setUnchar(int i, unsigned char newBits) { bits[i] = newBits; } + +bool BitArray::isRange(int start, int end, bool value, ErrorHandler &err_handler) { + if (end < start) { + err_handler = IllegalArgumentErrorHandler("isRange"); + return false; + } + if (start < 0 || end >= bits->size()) { + err_handler = IllegalArgumentErrorHandler("isRange"); + return false; + } + if (end == start) { + return true; // empty range matches + } + + bool startBool = bits[start] != (unsigned char)0; + + int end2 = start; + + if (startBool) { + end2 = getNextUnset(start); + } else { + end2 = getNextSet(start); + } + + if (startBool == value) { + if (end2 < end) { + return false; + } + } else { + return false; + } + + return true; +} + +void BitArray::reverse() { + bool *rowBits = getRowBoolPtr(); + bool tempBit; + + for (int i = 0; i < size / 2; i++) { + tempBit = rowBits[i]; + rowBits[i] = rowBits[size - i - 1]; + rowBits[size - i - 1] = tempBit; + } +} + +void BitArray::initAllNextSets() { + bool *rowBits = getRowBoolPtr(); + + int *nextSetArray = nextSets->data(); + int *nextUnsetArray = nextUnSets->data(); + + // Init the last one + if (rowBits[size - 1]) { + nextSetArray[size - 1] = size - 1; + nextUnsetArray[size - 1] = size; + } else { + nextUnsetArray[size - 1] = size - 1; + nextSetArray[size - 1] = size; + } + + // do inits + for (int i = size - 2; i >= 0; i--) { + if (rowBits[i]) { + nextSetArray[i] = i; + nextUnsetArray[i] = nextUnsetArray[i + 1]; + } else { + nextUnsetArray[i] = i; + nextSetArray[i] = nextSetArray[i + 1]; + } + } +} + +void BitArray::initAllNextSetsFromCounters(std::vector counters) { + bool *rowBits = getRowBoolPtr(); + bool isWhite = rowBits[0]; + int c = 0; + int offset = 0; + int count = 0; + int prevCount = 0; + int currCount = 0; + int _size = counters.size(); + + int *nextSetArray = nextSets->data(); + int *nextUnsetArray = nextUnSets->data(); + + // int* countersArray = counters.data(); + int *countersArray = &counters[0]; + + while (c < _size) { + currCount = countersArray[c]; + + count += currCount; + + if (isWhite) { + for (int i = 0; i < currCount; i++) { + offset = prevCount + i; + nextSetArray[offset] = prevCount + i; + nextUnsetArray[offset] = count; + } + } else { + for (int i = 0; i < currCount; i++) { + offset = prevCount + i; + nextSetArray[offset] = count; + nextUnsetArray[offset] = prevCount + i; + } + } + + isWhite = !isWhite; + + prevCount += currCount; + + c++; + } +} + +int BitArray::getNextSet(int from) { + if (from >= size) { + return size; + } + return nextSets[from]; +} + +int BitArray::getNextUnset(int from) { + if (from >= size) { + return size; + } + return nextUnSets[from]; +} + +BitArray::~BitArray() {} + +int BitArray::getSize() const { return size; } + +void BitArray::clear() { + int max = bits->size(); + for (int i = 0; i < max; i++) { + bits[i] = 0; + } +} + +BitArray::Reverse::Reverse(Ref array_) : array(array_) { array->reverse(); } + +BitArray::Reverse::~Reverse() { array->reverse(); } + +void BitArray::appendBit(bool value) { + ArrayRef newBits(size + 1); + for (int i = 0; i < size; i++) { + newBits[i] = bits[i]; + } + bits = newBits; + if (value) { + set(size); + } + ++size; +} + +int BitArray::getSizeInBytes() const { return 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. +void BitArray::appendBits(int value, int numBits, ErrorHandler &err_handler) { + if (numBits < 0 || numBits > 32) { + err_handler = IllegalArgumentErrorHandler("Number of bits must be between 0 and 32"); + return; + } + ArrayRef newBits(size + numBits); + for (int i = 0; i < size; i++) newBits[i] = bits[i]; + bits = newBits; + for (int numBitsLeft = numBits; numBitsLeft > 0; numBitsLeft--) { + if (((value >> (numBitsLeft - 1)) & 0x01) == 1) { + set(size); + } + ++size; + } + return; +} + +void BitArray::appendBitArray(const BitArray &array) { + ArrayRef newBits(size + array.getSize()); + for (int i = 0; i < size; ++i) { + newBits[i] = bits[i]; + } + bits = newBits; + for (int i = 0; i < array.getSize(); ++i) { + if (array.get(i)) { + set(size); + } + ++size; + } +} + +void BitArray::toBytes(int bitOffset, ArrayRef &array, int offset, int numBytes) { + for (int i = 0; i < numBytes; i++) { + int theByte = 0; + if (get(bitOffset)) { + theByte = 1; + } + bitOffset++; + array[offset + i] = theByte; + } +} +void BitArray::bitXOR(const BitArray &other, ErrorHandler &err_handler) { + if (size != other.size) { + err_handler = IllegalArgumentErrorHandler("Sizes don't match"); + return; + } + + for (int i = 0; i < bits->size(); i++) { + bits[i] = bits[i] == other.bits[i] ? 0 : 1; + } +} diff --git a/modules/wechat_qrcode/src/zxing/common/bitarray.hpp b/modules/wechat_qrcode/src/zxing/common/bitarray.hpp new file mode 100644 index 0000000..b6f1f56 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/bitarray.hpp @@ -0,0 +1,88 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_BITARRAY_HPP__ +#define __ZXING_COMMON_BITARRAY_HPP__ + +#include "../errorhandler.hpp" +#include "../zxing.hpp" +#include "array.hpp" +#include "counted.hpp" +#include + +namespace zxing { + +class BitArray : public Counted { +private: + int size; + ArrayRef bits; + ArrayRef nextSets; + ArrayRef nextUnSets; + // bool nextSetsInited; + +public: + explicit BitArray(int size); + ~BitArray(); + int getSize() const; + + bool get(int i) const { return bits[i] != 0; } + void set(int i) { + // bits[i] |= 0xFF; + bits[i] = true; + } + void setOneRow(unsigned char* rowBits, int length) { + unsigned char* dst = bits->data(); + memcpy(dst, rowBits, length); + } + + bool* getRowBoolPtr() { + // return (bool*)bits.data(); + return (bool*)bits->data(); + } + + // Init for next sets and unsets to speed up + void initAllNextSets(); + void initAllNextSetsFromCounters(std::vector counters); + + int getNextSet(int from); + int getNextUnset(int from); + + void setUnchar(int i, unsigned char newBist); + + void clear(); + bool isRange(int start, int end, bool value, ErrorHandler& err_handler); + + void reverse(); + + class Reverse { + private: + Ref array; + + public: + explicit Reverse(Ref array); + ~Reverse(); + }; + + void appendBit(bool value); + int getSizeInBytes() const; + void appendBits(int value, int numberOfBits, ErrorHandler& err_handler); + void appendBitArray(const BitArray& array); + void toBytes(int bitOffset, ArrayRef& array, int offset, int numBytes); + void bitXOR(const BitArray& other, ErrorHandler& err_handler); + +#ifndef USE_BYTE_FOR_BIT +private: + static int makeArraySize(int size); +#endif +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_BITARRAY_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/bitmatrix.cpp b/modules/wechat_qrcode/src/zxing/common/bitmatrix.cpp new file mode 100644 index 0000000..53df192 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/bitmatrix.cpp @@ -0,0 +1,397 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "bitmatrix.hpp" + +using zxing::ArrayRef; +using zxing::BitArray; +using zxing::BitMatrix; +using zxing::ErrorHandler; +using zxing::Ref; + +void BitMatrix::init(int _width, int _height, ErrorHandler& err_handler) { + if (_width < 1 || _height < 1) { + err_handler = IllegalArgumentErrorHandler("Both dimensions must be greater than 0"); + return; + } + width = _width; + height = _height; + this->rowBitsSize = width; + bits = ArrayRef(width * height); + rowOffsets = ArrayRef(height); + + // offsetRowSize = new int[height]; + rowOffsets[0] = 0; + for (int i = 1; i < height; i++) { + rowOffsets[i] = rowOffsets[i - 1] + width; + } + + isInitRowCounters = false; + isInitColsCounters = false; +} + +void BitMatrix::init(int _width, int _height, unsigned char* bitsPtr, ErrorHandler& err_handler) { + init(_width, _height, err_handler); + if (err_handler.ErrCode()) return; + memcpy(bits->data(), bitsPtr, width * height * sizeof(unsigned char)); +} + +void BitMatrix::initRowCounters() { + if (isInitRowCounters == true) { + return; + } + + row_counters = vector(width * height, 0); + row_counters_offset = vector(width * height, 0); + row_point_offset = vector(width * height, 0); + row_counter_offset_end = vector(height, 0); + + row_counters_recorded = vector(height, false); + + isInitRowCounters = true; +} +void BitMatrix::initColsCounters() { + if (isInitColsCounters == true) { + return; + } + + cols_counters = vector(width * height, 0); + cols_counters_offset = vector(width * height, 0); + cols_point_offset = vector(width * height, 0); + cols_counter_offset_end = vector(width, 0); + + cols_counters_recorded = vector(width, false); + + isInitColsCounters = true; +} + +BitMatrix::BitMatrix(int dimension, ErrorHandler& err_handler) { + init(dimension, dimension, err_handler); +} + +BitMatrix::BitMatrix(int _width, int _height, ErrorHandler& err_handler) { + init(_width, _height, err_handler); +} + +BitMatrix::BitMatrix(int _width, int _height, unsigned char* bitsPtr, ErrorHandler& err_handler) { + init(_width, _height, bitsPtr, err_handler); +} +// Copy bitMatrix +void BitMatrix::copyOf(Ref _bits, ErrorHandler& err_handler) { + int _width = _bits->getWidth(); + int _height = _bits->getHeight(); + init(_width, _height, err_handler); + + for (int y = 0; y < height; y++) { + bool* rowPtr = _bits->getRowBoolPtr(y); + setRowBool(y, rowPtr); + } +} + +void BitMatrix::xxor(Ref _bits) { + if (width != _bits->getWidth() || height != _bits->getHeight()) { + return; + } + + for (int y = 0; y < height && y < _bits->getHeight(); ++y) { + bool* rowPtrA = _bits->getRowBoolPtr(y); + bool* rowPtrB = getRowBoolPtr(y); + + for (int x = 0; x < width && x < _bits->getWidth(); ++x) { + rowPtrB[x] = rowPtrB[x] ^ rowPtrA[x]; + } + setRowBool(y, rowPtrB); + } +} + +BitMatrix::~BitMatrix() {} + +void BitMatrix::flip(int x, int y) { + bits[rowOffsets[y] + x] = (bits[rowOffsets[y] + x] == (unsigned char)0); +} + +void BitMatrix::flipAll() { + bool* matrixBits = (bool*)bits->data(); + for (int i = 0; i < bits->size(); i++) { + matrixBits[i] = !matrixBits[i]; + } +} + +void BitMatrix::flipRegion(int left, int top, int _width, int _height, ErrorHandler& err_handler) { + if (top < 0 || left < 0) { + err_handler = IllegalArgumentErrorHandler("Left and top must be nonnegative"); + return; + } + if (_height < 1 || _width < 1) { + err_handler = IllegalArgumentErrorHandler("Height and width must be at least 1"); + return; + } + int right = left + _width; + int bottom = top + _height; + if (bottom > this->height || right > this->width) { + err_handler = IllegalArgumentErrorHandler("The region must fit inside the matrix"); + return; + } + + for (int y = top; y < bottom; y++) { + for (int x = left; x < right; x++) { + bits[rowOffsets[y] + x] ^= 1; + } + } +} + +void BitMatrix::setRegion(int left, int top, int _width, int _height, ErrorHandler& err_handler) { + if (top < 0 || left < 0) { + err_handler = IllegalArgumentErrorHandler("Left and top must be nonnegative"); + return; + } + if (_height < 1 || _width < 1) { + err_handler = IllegalArgumentErrorHandler("Height and width must be at least 1"); + return; + } + int right = left + _width; + int bottom = top + _height; + if (bottom > this->height || right > this->width) { + err_handler = IllegalArgumentErrorHandler("The region must fit inside the matrix"); + return; + } + + for (int y = top; y < bottom; y++) { + for (int x = left; x < right; x++) { + bits[rowOffsets[y] + x] = true; + // bits[rowOffsets[y]+x] |= 0xFF; + } + } +} + +Ref BitMatrix::getRow(int y, Ref row) { + if (row.empty() || row->getSize() < width) { + row = new BitArray(width); + } + + // row-> + unsigned char* src = bits.data() + rowOffsets[y]; + row->setOneRow(src, width); + + return row; +} + +ArrayRef BitMatrix::getTopLeftOnBit() const { + int bitsOffset = 0; + while (bitsOffset < bits->size() && bits[bitsOffset] == 0) { + bitsOffset++; + } + if (bitsOffset == bits->size()) { + return ArrayRef(); + } + int y = bitsOffset / width; + int x = bitsOffset % width; + ArrayRef res(2); + res[0] = x; + res[1] = y; + return res; +} + +ArrayRef BitMatrix::getBottomRightOnBit() const { + int bitsOffset = bits->size() - 1; + while (bitsOffset >= 0 && bits[bitsOffset] == 0) { + bitsOffset--; + } + if (bitsOffset < 0) { + return ArrayRef(); + } + + int y = bitsOffset / width; + int x = bitsOffset % width; + ArrayRef res(2); + res[0] = x; + res[1] = y; + return res; +} + +void BitMatrix::getRowBool(int y, bool* getrow) { + int offset = rowOffsets[y]; + unsigned char* src = bits.data() + offset; + memcpy(getrow, src, rowBitsSize * sizeof(bool)); +} + +void BitMatrix::setRowBool(int y, bool* row) { + int offset = rowOffsets[y]; + unsigned char* dst = bits.data() + offset; + memcpy(dst, row, rowBitsSize * sizeof(bool)); + + return; +} + +bool* BitMatrix::getRowBoolPtr(int y) { + int offset = y * rowBitsSize; + unsigned char* src = bits.data() + offset; + return (bool*)src; +} + +void BitMatrix::clear() { + int size = bits->size(); + + unsigned char* dst = bits->data(); + memset(dst, 0, size * sizeof(unsigned char)); +} + +int BitMatrix::getWidth() const { return width; } + +int BitMatrix::getHeight() const { return height; } + +COUNTER_TYPE* BitMatrix::getRowPointInRecords(int y) { + if (!row_point_offset[y]) { + setRowRecords(y); + } + int offset = y * width; + COUNTER_TYPE* counters = &row_point_offset[0] + offset; + return (COUNTER_TYPE*)counters; +} + +COUNTER_TYPE* BitMatrix::getRowRecords(int y) { + if (!row_counters_recorded[y]) { + setRowRecords(y); + } + int offset = y * width; + COUNTER_TYPE* counters = &row_counters[0] + offset; + return (COUNTER_TYPE*)counters; +} + +COUNTER_TYPE* BitMatrix::getRowRecordsOffset(int y) { + if (!row_counters_recorded[y]) { + setRowRecords(y); + } + int offset = y * width; + COUNTER_TYPE* counters = &row_counters_offset[0] + offset; + return (COUNTER_TYPE*)counters; +} + +bool BitMatrix::getRowFirstIsWhite(int y) { + bool is_white = !get(0, y); + return is_white; +} + +bool BitMatrix::getRowLastIsWhite(int y) { + bool last_is_white = !get(width - 1, y); + return last_is_white; +} + +COUNTER_TYPE BitMatrix::getRowCounterOffsetEnd(int y) { + if (!row_counters_recorded[y]) { + setRowRecords(y); + } + return row_counter_offset_end[y]; +} + +void BitMatrix::setRowRecords(int y) { + COUNTER_TYPE* cur_row_counters = &row_counters[0] + y * width; + COUNTER_TYPE* cur_row_counters_offset = &row_counters_offset[0] + y * width; + COUNTER_TYPE* cur_row_point_in_counters = &row_point_offset[0] + y * width; + int end = width; + + bool* rowBit = getRowBoolPtr(y); + bool isWhite = !rowBit[0]; + int counterPosition = 0; + int i = 0; + cur_row_counters_offset[0] = 0; + while (i < end) { + if (rowBit[i] ^ isWhite) { // that is, exactly one is true + cur_row_counters[counterPosition]++; + } else { + counterPosition++; + if (counterPosition == end) { + break; + } else { + cur_row_counters[counterPosition] = 1; + isWhite = !isWhite; + cur_row_counters_offset[counterPosition] = i; + } + } + cur_row_point_in_counters[i] = counterPosition; + i++; + } + + // use the last row__onedReaderData->counter_size to record + // _onedReaderData->counter_size + row_counter_offset_end[y] = counterPosition < end ? (counterPosition + 1) : end; + + row_counters_recorded[y] = true; + return; +} + +COUNTER_TYPE* BitMatrix::getColsPointInRecords(int x) { + if (!cols_point_offset[x]) { + setColsRecords(x); + } + int offset = x * height; + COUNTER_TYPE* counters = &cols_point_offset[0] + offset; + return (COUNTER_TYPE*)counters; +} + +COUNTER_TYPE* BitMatrix::getColsRecords(int x) { + if (!cols_counters_recorded[x]) { + setColsRecords(x); + } + int offset = x * height; + COUNTER_TYPE* counters = &cols_counters[0] + offset; + return (COUNTER_TYPE*)counters; +} + +COUNTER_TYPE* BitMatrix::getColsRecordsOffset(int x) { + if (!cols_counters_recorded[x]) { + setColsRecords(x); + } + int offset = x * height; + COUNTER_TYPE* counters = &cols_counters_offset[0] + offset; + return (COUNTER_TYPE*)counters; +} + +COUNTER_TYPE BitMatrix::getColsCounterOffsetEnd(int x) { + if (!cols_counters_recorded[x]) { + setColsRecords(x); + } + return cols_counter_offset_end[x]; +} + +void BitMatrix::setColsRecords(int x) { + COUNTER_TYPE* cur_cols_counters = &cols_counters[0] + x * height; + COUNTER_TYPE* cur_cols_counters_offset = &cols_counters_offset[0] + x * height; + COUNTER_TYPE* cur_cols_point_in_counters = &cols_point_offset[0] + x * height; + int end = height; + + bool* rowBit = getRowBoolPtr(0); + bool isWhite = !rowBit[0]; + int counterPosition = 0; + int i = 0; + cur_cols_counters_offset[0] = 0; + while (i < end) { + if (rowBit[i] ^ isWhite) { // that is, exactly one is true + cur_cols_counters[counterPosition]++; + } else { + counterPosition++; + if (counterPosition == end) { + break; + } else { + cur_cols_counters[counterPosition] = 1; + isWhite = !isWhite; + cur_cols_counters_offset[counterPosition] = i; + } + } + cur_cols_point_in_counters[i] = counterPosition; + i++; + rowBit += width; + } + + cols_counter_offset_end[x] = counterPosition < end ? (counterPosition + 1) : end; + + cols_counters_recorded[x] = true; + return; +}; diff --git a/modules/wechat_qrcode/src/zxing/common/bitmatrix.hpp b/modules/wechat_qrcode/src/zxing/common/bitmatrix.hpp new file mode 100644 index 0000000..3cf7ec3 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/bitmatrix.hpp @@ -0,0 +1,115 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_BITMATRIX_HPP__ +#define __ZXING_COMMON_BITMATRIX_HPP__ + +#include "../errorhandler.hpp" +#include "array.hpp" +#include "bitarray.hpp" +#include "counted.hpp" +using namespace std; + +namespace zxing { + +class BitMatrix : public Counted { +public: + static const int bitsPerWord = std::numeric_limits::digits; + +private: + int width; + int height; + int rowBitsSize; + + vector row_counters; + vector row_counters_offset; + vector row_counters_recorded; + vector row_counter_offset_end; + vector row_point_offset; + + vector cols_counters; + vector cols_counters_offset; + vector cols_counters_recorded; + vector cols_counter_offset_end; + vector cols_point_offset; + + ArrayRef bits; + ArrayRef rowOffsets; + +public: + BitMatrix(int _width, int _height, unsigned char* bitsPtr, ErrorHandler& err_handler); + BitMatrix(int dimension, ErrorHandler& err_handler); + BitMatrix(int _width, int _height, ErrorHandler& err_handler); + + void copyOf(Ref _bits, ErrorHandler& err_handler); + void xxor(Ref _bits); + + ~BitMatrix(); + + unsigned char get(int x, int y) const { return bits[width * y + x]; } + + void set(int x, int y) { bits[rowOffsets[y] + x] = (unsigned char)1; } + + void set(int x, int y, unsigned char value) { bits[rowOffsets[y] + x] = value; } + + void swap(int srcX, int srcY, int dstX, int dstY) { + auto temp = bits[width * srcY + srcX]; + bits[width * srcY + srcX] = bits[width * dstY + dstX]; + bits[width * dstY + dstX] = temp; + } + + void getRowBool(int y, bool* row); + bool* getRowBoolPtr(int y); + void setRowBool(int y, bool* row); + int getRowBitsSize() { return rowBitsSize; } + unsigned char* getPtr() { return bits->data(); } + + void flip(int x, int y); + void flipAll(); + void clear(); + void setRegion(int left, int top, int _width, int _height, ErrorHandler& err_handler); + void flipRegion(int left, int top, int _width, int _height, ErrorHandler& err_handler); + Ref getRow(int y, Ref row); + + int getWidth() const; + int getHeight() const; + + ArrayRef getTopLeftOnBit() const; + ArrayRef getBottomRightOnBit() const; + + bool isInitRowCounters; + void initRowCounters(); + COUNTER_TYPE* getRowRecords(int y); + COUNTER_TYPE* getRowRecordsOffset(int y); + bool getRowFirstIsWhite(int y); + COUNTER_TYPE getRowCounterOffsetEnd(int y); + bool getRowLastIsWhite(int y); + COUNTER_TYPE* getRowPointInRecords(int y); + + bool isInitColsCounters; + void initColsCounters(); + COUNTER_TYPE* getColsRecords(int x); + COUNTER_TYPE* getColsRecordsOffset(int x); + COUNTER_TYPE* getColsPointInRecords(int x); + COUNTER_TYPE getColsCounterOffsetEnd(int x); + +private: + inline void init(int, int, ErrorHandler& err_handler); + inline void init(int _width, int _height, unsigned char* bitsPtr, ErrorHandler& err_handler); + + void setRowRecords(int y); + void setColsRecords(int x); + + BitMatrix(const BitMatrix&, ErrorHandler& err_handler); +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_BITMATRIX_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/bitsource.cpp b/modules/wechat_qrcode/src/zxing/common/bitsource.cpp new file mode 100644 index 0000000..52187e1 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/bitsource.cpp @@ -0,0 +1,62 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "bitsource.hpp" +#include + +namespace zxing { + +int BitSource::readBits(int numBits, ErrorHandler& err_handler) { + if (numBits < 0 || numBits > 32 || numBits > available()) { + std::ostringstream oss; + oss << numBits; + err_handler = IllegalArgumentErrorHandler(oss.str().c_str()); + return -1; + } + + int result = 0; + + // First, read remainder from current byte + if (bitOffset_ > 0) { + int bitsLeft = 8 - bitOffset_; + int toRead = numBits < bitsLeft ? numBits : bitsLeft; + int bitsToNotRead = bitsLeft - toRead; + int mask = (0xFF >> (8 - toRead)) << bitsToNotRead; + result = (bytes_[byteOffset_] & mask) >> bitsToNotRead; + numBits -= toRead; + bitOffset_ += toRead; + if (bitOffset_ == 8) { + bitOffset_ = 0; + byteOffset_++; + } + } + + // Next read whole bytes + if (numBits > 0) { + while (numBits >= 8) { + result = (result << 8) | (bytes_[byteOffset_] & 0xFF); + byteOffset_++; + numBits -= 8; + } + + // Finally read a partial byte + if (numBits > 0) { + int bitsToNotRead = 8 - numBits; + int mask = (0xFF >> bitsToNotRead) << bitsToNotRead; + result = (result << numBits) | ((bytes_[byteOffset_] & mask) >> bitsToNotRead); + bitOffset_ += numBits; + } + } + + return result; +} + +int BitSource::available() { return 8 * (bytes_->size() - byteOffset_) - bitOffset_; } +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/common/bitsource.hpp b/modules/wechat_qrcode/src/zxing/common/bitsource.hpp new file mode 100644 index 0000000..797c2b5 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/bitsource.hpp @@ -0,0 +1,57 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_BITSOURCE_HPP__ +#define __ZXING_COMMON_BITSOURCE_HPP__ + +#include "../errorhandler.hpp" +#include "array.hpp" + +namespace zxing { +/** + *

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 not thread-safe.

+ * + * @author srowen@google.com (Sean Owen) + * @author christian.brunschen@gmail.com (Christian Brunschen) + */ +class BitSource : public Counted { + typedef char byte; + +private: + ArrayRef bytes_; + int byteOffset_; + int bitOffset_; + +public: + /** + * @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. + */ + explicit BitSource(ArrayRef &bytes) : bytes_(bytes), byteOffset_(0), bitOffset_(0) {} + + int getBitOffset() { return bitOffset_; } + + int getByteOffset() { return byteOffset_; } + + int readBits(int numBits, ErrorHandler &err_handler); + + /** + * @return number of bits that can be read successfully + */ + int available(); +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_BITSOURCE_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/bytematrix.cpp b/modules/wechat_qrcode/src/zxing/common/bytematrix.cpp new file mode 100644 index 0000000..61cf251 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/bytematrix.cpp @@ -0,0 +1,50 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "../../precomp.hpp" +#include "bytematrix.hpp" + +using zxing::ArrayRef; +using zxing::ByteMatrix; +using zxing::ErrorHandler; +using zxing::Ref; + +void ByteMatrix::init(int _width, int _height) { + if (_width < 1 || _height < 1) { + return; + } + this->width = _width; + this->height = _height; + bytes = new unsigned char[width * height]; + row_offsets = new int[height]; + row_offsets[0] = 0; + for (int i = 1; i < height; i++) { + row_offsets[i] = row_offsets[i - 1] + width; + } +} + +ByteMatrix::ByteMatrix(int dimension) { init(dimension, dimension); } + +ByteMatrix::ByteMatrix(int _width, int _height) { init(_width, _height); } + +ByteMatrix::ByteMatrix(int _width, int _height, ArrayRef source) { + init(_width, _height); + int size = _width * _height; + memcpy(&bytes[0], &source[0], size); +} + +ByteMatrix::~ByteMatrix() { + if (bytes) delete[] bytes; + if (row_offsets) delete[] row_offsets; +} + +unsigned char* ByteMatrix::getByteRow(int y, ErrorHandler& err_handler) { + if (y < 0 || y >= getHeight()) { + err_handler = IllegalArgumentErrorHandler("Requested row is outside the image."); + return NULL; + } + return &bytes[row_offsets[y]]; +} diff --git a/modules/wechat_qrcode/src/zxing/common/bytematrix.hpp b/modules/wechat_qrcode/src/zxing/common/bytematrix.hpp new file mode 100644 index 0000000..c3ee35e --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/bytematrix.hpp @@ -0,0 +1,58 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __ZXING_COMMON_BYTEMATRIX_HPP__ +#define __ZXING_COMMON_BYTEMATRIX_HPP__ + +#include "../errorhandler.hpp" +#include "array.hpp" +#include "bitarray.hpp" +#include "counted.hpp" + +namespace zxing { + +class ByteMatrix : public Counted { +public: + explicit ByteMatrix(int dimension); + ByteMatrix(int _width, int _height); + ByteMatrix(int _width, int _height, ArrayRef source); + ~ByteMatrix(); + + char get(int x, int y) const { + int offset = row_offsets[y] + x; + return bytes[offset]; + } + + void set(int x, int y, char char_value) { + int offset = row_offsets[y] + x; + bytes[offset] = char_value & 0XFF; + } + + unsigned char* getByteRow(int y, ErrorHandler& err_handler); + + int getWidth() const { return width; } + int getHeight() const { return height; } + + unsigned char* bytes; + +private: + int width; + int height; + + // ArrayRef bytes; + // ArrayRef row_offsets; + int* row_offsets; + +private: + inline void init(int, int); + ByteMatrix(const ByteMatrix&); + ByteMatrix& operator=(const ByteMatrix&); +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_BYTEMATRIX_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/characterseteci.cpp b/modules/wechat_qrcode/src/zxing/common/characterseteci.cpp new file mode 100644 index 0000000..acef204 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/characterseteci.cpp @@ -0,0 +1,111 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "characterseteci.hpp" +using zxing::common::CharacterSetECI; + +// Fix memory leak +// https://github.com/ukeller/zxing-cpp/commit/c632ffe47ca7342f894ae533263be249cbdfd37e +// std::map CharacterSetECI::VALUE_TO_ECI; +// std::map CharacterSetECI::NAME_TO_ECI; +std::map > CharacterSetECI::VALUE_TO_ECI; +std::map > CharacterSetECI::NAME_TO_ECI; + +const bool CharacterSetECI::inited = CharacterSetECI::init_tables(); + +#define ADD_CHARACTER_SET(VALUES, STRINGS) \ + { \ + static int values[] = {VALUES, -1}; \ + static char const* strings[] = {STRINGS, 0}; \ + addCharacterSet(values, strings); \ + } + +#define XC , + +bool CharacterSetECI::init_tables() { + ADD_CHARACTER_SET(0 XC 2, "Cp437"); + ADD_CHARACTER_SET(1 XC 3, "ISO8859_1" XC "ISO-8859-1"); + ADD_CHARACTER_SET(4, "ISO8859_2" XC "ISO-8859-2"); + ADD_CHARACTER_SET(5, "ISO8859_3" XC "ISO-8859-3"); + ADD_CHARACTER_SET(6, "ISO8859_4" XC "ISO-8859-4"); + ADD_CHARACTER_SET(7, "ISO8859_5" XC "ISO-8859-5"); + ADD_CHARACTER_SET(8, "ISO8859_6" XC "ISO-8859-6"); + ADD_CHARACTER_SET(9, "ISO8859_7" XC "ISO-8859-7"); + ADD_CHARACTER_SET(10, "ISO8859_8" XC "ISO-8859-8"); + ADD_CHARACTER_SET(11, "ISO8859_9" XC "ISO-8859-9"); + ADD_CHARACTER_SET(12, "ISO8859_10" XC "ISO-8859-10"); + ADD_CHARACTER_SET(13, "ISO8859_11" XC "ISO-8859-11"); + ADD_CHARACTER_SET(15, "ISO8859_13" XC "ISO-8859-13"); + ADD_CHARACTER_SET(16, "ISO8859_14" XC "ISO-8859-14"); + ADD_CHARACTER_SET(17, "ISO8859_15" XC "ISO-8859-15"); + ADD_CHARACTER_SET(18, "ISO8859_16" XC "ISO-8859-16"); + ADD_CHARACTER_SET(20, "SJIS" XC "Shift_JIS"); + ADD_CHARACTER_SET(21, "Cp1250" XC "windows-1250"); + ADD_CHARACTER_SET(22, "Cp1251" XC "windows-1251"); + ADD_CHARACTER_SET(23, "Cp1252" XC "windows-1252"); + ADD_CHARACTER_SET(24, "Cp1256" XC "windows-1256"); + ADD_CHARACTER_SET(25, "UnicodeBigUnmarked" XC "UTF-16BE" XC "UnicodeBig"); + ADD_CHARACTER_SET(26, "UTF8" XC "UTF-8"); + ADD_CHARACTER_SET(27 XC 170, "ASCII" XC "US-ASCII"); + ADD_CHARACTER_SET(28, "Big5"); + ADD_CHARACTER_SET(29, "GB18030" XC "GB2312" XC "EUC_CN" XC "GBK"); + ADD_CHARACTER_SET(30, "EUC_KR" XC "EUC-KR"); + return true; +} + +#undef XC + +CharacterSetECI::CharacterSetECI(int const* values, char const* const* names) + : values_(values), names_(names) { + zxing::Ref this_ref(this); + + for (int const* p_values = values_; *p_values != -1; p_values++) { + // VALUE_TO_ECI[*values] = this; + VALUE_TO_ECI[*p_values] = this_ref; + } + for (char const* const* p_names = names_; *p_names; p_names++) { + // NAME_TO_ECI[string(*names)] = this; + NAME_TO_ECI[string(*p_names)] = this_ref; + } +} + +char const* CharacterSetECI::name() const { return names_[0]; } + +int CharacterSetECI::getValue() const { return values_[0]; } + +void CharacterSetECI::addCharacterSet(int const* values, char const* const* names) { + new CharacterSetECI(values, names); +} + +CharacterSetECI* CharacterSetECI::getCharacterSetECIByValueFind(int value) { + if (value < 0 || value >= 900) { + return zxing::Ref(0); + } + + std::map >::iterator iter; + iter = VALUE_TO_ECI.find(value); + + if (iter != VALUE_TO_ECI.end()) { + return iter->second; + } else { + return zxing::Ref(0); + } +} + +CharacterSetECI* CharacterSetECI::getCharacterSetECIByName(string const& name) { + std::map >::iterator iter; + iter = NAME_TO_ECI.find(name); + + if (iter != NAME_TO_ECI.end()) { + return iter->second; + } else { + return zxing::Ref(0); + } +} diff --git a/modules/wechat_qrcode/src/zxing/common/characterseteci.hpp b/modules/wechat_qrcode/src/zxing/common/characterseteci.hpp new file mode 100644 index 0000000..9b44f14 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/characterseteci.hpp @@ -0,0 +1,46 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_CHARACTERSETECI_HPP__ +#define __ZXING_COMMON_CHARACTERSETECI_HPP__ + +#include +#include "../decodehints.hpp" +#include "counted.hpp" + +namespace zxing { +namespace common { + +class CharacterSetECI : public Counted { +private: + static std::map > VALUE_TO_ECI; + static std::map > NAME_TO_ECI; + static const bool inited; + static bool init_tables(); + + int const* const values_; + char const* const* const names_; + + CharacterSetECI(int const* values, char const* const* names); + + static void addCharacterSet(int const* value, char const* const* encodingNames); + +public: + char const* name() const; + int getValue() const; + + static CharacterSetECI* getCharacterSetECIByValueFind(int value); + static CharacterSetECI* getCharacterSetECIByName(std::string const& name); +}; + +} // namespace common +} // namespace zxing + +#endif // __ZXING_COMMON_CHARACTERSETECI_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/counted.hpp b/modules/wechat_qrcode/src/zxing/common/counted.hpp new file mode 100644 index 0000000..d40d62a --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/counted.hpp @@ -0,0 +1,110 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_COUNTED_HPP__ +#define __ZXING_COMMON_COUNTED_HPP__ + +#include +#include +namespace zxing { + +/* base class for reference-counted objects */ +class Counted { +private: + unsigned int count_; + +public: + Counted() : count_(0) {} + virtual ~Counted() {} + Counted* retain() { + count_++; + return this; + } + void release() { + count_--; + if (count_ == 0) { + count_ = 0xDEADF001; + delete this; + } + } + + /* return the current count for denugging purposes or similar */ + int count() const { return count_; } +}; + +/* counting reference to reference-counted objects */ +template +class Ref { +private: +public: + T* object_; + explicit Ref(T* o = 0) : object_(0) { reset(o); } + Ref(const Ref& other) : object_(0) { reset(other.object_); } + + template + Ref(const Ref& other) : object_(0) { + reset(other.object_); + } + + ~Ref() { + if (object_) { + object_->release(); + } + } + + void reset(T* o) { + if (o) { + o->retain(); + } + if (object_ != 0) { + object_->release(); + } + object_ = o; + } + Ref& operator=(const Ref& other) { + reset(other.object_); + return *this; + } + template + Ref& operator=(const Ref& other) { + reset(other.object_); + return *this; + } + Ref& operator=(T* o) { + reset(o); + return *this; + } + template + Ref& operator=(Y* o) { + reset(o); + return *this; + } + + T& operator*() { return *object_; } + T* operator->() const { return object_; } + operator T*() const { return object_; } + + bool operator==(const T* that) { return object_ == that; } + bool operator==(const Ref& other) const { + return object_ == other.object_ || *object_ == *(other.object_); + } + template + bool operator==(const Ref& other) const { + return object_ == other.object_ || *object_ == *(other.object_); + } + + bool operator!=(const T* that) { return !(*this == that); } + + bool empty() const { return object_ == 0; } +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_COUNTED_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/decoder_result.cpp b/modules/wechat_qrcode/src/zxing/common/decoder_result.cpp new file mode 100644 index 0000000..3de6656 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/decoder_result.cpp @@ -0,0 +1,65 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "decoder_result.hpp" + +using zxing::DecoderResult; +using zxing::Ref; +using zxing::ArrayRef; +using zxing::String; +DecoderResult::DecoderResult(ArrayRef rawBytes, Ref text, + ArrayRef >& byteSegments, string const& ecLevel) + : rawBytes_(rawBytes), text_(text), byteSegments_(byteSegments), ecLevel_(ecLevel) { + outputCharset_ = "UTF-8"; + otherClassName = ""; + qrcodeVersion_ = -1; +} + +DecoderResult::DecoderResult(ArrayRef rawBytes, Ref text, + ArrayRef >& byteSegments, string const& ecLevel, + string outputCharset) + : rawBytes_(rawBytes), + text_(text), + byteSegments_(byteSegments), + ecLevel_(ecLevel), + outputCharset_(outputCharset) { + otherClassName = ""; + qrcodeVersion_ = -1; +} + +DecoderResult::DecoderResult(ArrayRef rawBytes, Ref text, + ArrayRef >& byteSegments, string const& ecLevel, + string outputCharset, int qrcodeVersion, string& charsetMode) + : rawBytes_(rawBytes), + text_(text), + byteSegments_(byteSegments), + ecLevel_(ecLevel), + outputCharset_(outputCharset), + qrcodeVersion_(qrcodeVersion), + charsetMode_(charsetMode) { + otherClassName = ""; +} + +DecoderResult::DecoderResult(ArrayRef rawBytes, Ref text) + : rawBytes_(rawBytes), text_(text) { + outputCharset_ = "UTF-8"; + otherClassName = ""; +} + +DecoderResult::DecoderResult(ArrayRef rawBytes, Ref text, std::string outputCharset) + : rawBytes_(rawBytes), text_(text), outputCharset_(outputCharset) { + otherClassName = ""; +} + +ArrayRef DecoderResult::getRawBytes() { return rawBytes_; } + +Ref DecoderResult::getText() { return text_; } + +string DecoderResult::getCharset() { return outputCharset_; } diff --git a/modules/wechat_qrcode/src/zxing/common/decoder_result.hpp b/modules/wechat_qrcode/src/zxing/common/decoder_result.hpp new file mode 100644 index 0000000..7ffd002 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/decoder_result.hpp @@ -0,0 +1,77 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_DECODER_RESULT_HPP__ +#define __ZXING_COMMON_DECODER_RESULT_HPP__ + +#include "../qrcode/decoder/qrcode_decoder_metadata.hpp" +#include "array.hpp" +#include "counted.hpp" +#include "str.hpp" + +namespace zxing { + +class DecoderResult : public Counted { +private: + ArrayRef rawBytes_; + Ref text_; + ArrayRef > byteSegments_; + std::string ecLevel_; + std::string outputCharset_; + int qrcodeVersion_; + std::string charsetMode_; + + Ref other_; + string otherClassName; + +public: + DecoderResult(ArrayRef rawBytes, Ref text, + ArrayRef >& byteSegments, std::string const& ecLevel); + + DecoderResult(ArrayRef rawBytes, Ref text, + ArrayRef >& byteSegments, std::string const& ecLevel, + std::string outputCharset); + + DecoderResult(ArrayRef rawBytes, Ref text, + ArrayRef >& byteSegments, std::string const& ecLevel, + std::string outputCharset, int qrcodeVersion, std::string& charsetMode); + + DecoderResult(ArrayRef rawBytes, Ref text); + + DecoderResult(ArrayRef rawBytes, Ref text, std::string outputCharset); + + ArrayRef getRawBytes(); + Ref getText(); + std::string getCharset(); + + void setOther(Ref other) { + other_ = other; + otherClassName = "QRCodeDecoderMetaData"; + }; + + Ref getOther() { + // className = otherClassName; + return other_; + }; + + string getOtherClassName() { return otherClassName; }; + + int getQRCodeVersion() const { return qrcodeVersion_; }; + + void setText(Ref text) { text_ = text; }; + + string getEcLevel() { return ecLevel_; } + + string getCharsetMode() { return charsetMode_; } +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_DECODER_RESULT_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/detector_result.cpp b/modules/wechat_qrcode/src/zxing/common/detector_result.cpp new file mode 100644 index 0000000..2e50a5d --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/detector_result.cpp @@ -0,0 +1,27 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "detector_result.hpp" + +namespace zxing { + +DetectorResult::DetectorResult(Ref bits, ArrayRef > points, + int dimension, float modulesize) + : bits_(bits), points_(points), dimension_(dimension), modulesize_(modulesize) {} + +void DetectorResult::SetGray(Ref gray) { gray_ = gray; } + +Ref DetectorResult::getBits() { return bits_; } + +Ref DetectorResult::getGray() { return gray_; } + +ArrayRef > DetectorResult::getPoints() { return points_; } + +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/common/detector_result.hpp b/modules/wechat_qrcode/src/zxing/common/detector_result.hpp new file mode 100644 index 0000000..c48e9ef --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/detector_result.hpp @@ -0,0 +1,42 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_DETECTOR_RESULT_HPP__ +#define __ZXING_COMMON_DETECTOR_RESULT_HPP__ + +#include "../resultpoint.hpp" +#include "array.hpp" +#include "bitmatrix.hpp" +#include "bytematrix.hpp" +#include "counted.hpp" + +namespace zxing { + +class DetectorResult : public Counted { +private: + Ref bits_; + Ref gray_; + ArrayRef > points_; + +public: + DetectorResult(Ref bits, ArrayRef > points, int dimension = 0, + float modulesize = 0); + DetectorResult(Ref gray, ArrayRef > points, int dimension = 0, + float modulesize = 0); + Ref getBits(); + Ref getGray(); + void SetGray(Ref gray); + ArrayRef > getPoints(); + int dimension_; + float modulesize_; +}; +} // namespace zxing + +#endif // __ZXING_COMMON_DETECTOR_RESULT_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/greyscale_luminance_source.cpp b/modules/wechat_qrcode/src/zxing/common/greyscale_luminance_source.cpp new file mode 100644 index 0000000..cf35ccb --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/greyscale_luminance_source.cpp @@ -0,0 +1,77 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "greyscale_luminance_source.hpp" +#include "bytematrix.hpp" +#include "greyscale_rotated_luminance_source.hpp" +using zxing::ArrayRef; +using zxing::ByteMatrix; +using zxing::ErrorHandler; +using zxing::GreyscaleLuminanceSource; +using zxing::LuminanceSource; +using zxing::Ref; + +GreyscaleLuminanceSource::GreyscaleLuminanceSource(ArrayRef greyData, int dataWidth, + int dataHeight, int left, int top, int width, + int height, ErrorHandler& err_handler) + : Super(width, height), + greyData_(greyData), + dataWidth_(dataWidth), + dataHeight_(dataHeight), + left_(left), + top_(top) { + if (left + width > dataWidth || top + height > dataHeight || top < 0 || left < 0) { + err_handler = IllegalArgumentErrorHandler("Crop rectangle does not fit within image data."); + } +} + +ArrayRef GreyscaleLuminanceSource::getRow(int y, ArrayRef row, + ErrorHandler& err_handler) const { + if (y < 0 || y >= this->getHeight()) { + err_handler = IllegalArgumentErrorHandler("Requested row is outside the image."); + return ArrayRef(); + } + int width = getWidth(); + if (!row || row->size() < width) { + ArrayRef temp(width); + row = temp; + } + int offset = (y + top_) * dataWidth_ + left_; + memcpy(&row[0], &greyData_[offset], width); + return row; +} + +ArrayRef GreyscaleLuminanceSource::getMatrix() const { + int size = getWidth() * getHeight(); + ArrayRef result(size); + if (left_ == 0 && top_ == 0 && dataWidth_ == getWidth() && dataHeight_ == getHeight()) { + memcpy(&result[0], &greyData_[0], size); + } else { + for (int row = 0; row < getHeight(); row++) { + memcpy(&result[row * getWidth()], &greyData_[(top_ + row) * dataWidth_ + left_], + getWidth()); + } + } + return result; +} + +Ref GreyscaleLuminanceSource::rotateCounterClockwise( + ErrorHandler& err_handler) const { + // Intentionally flip the left, top, width, and height arguments as + // needed. dataWidth and dataHeight are always kept unrotated. + Ref result(new GreyscaleRotatedLuminanceSource( + greyData_, dataWidth_, dataHeight_, top_, left_, getHeight(), getWidth(), err_handler)); + if (err_handler.ErrCode()) return Ref(); + return result; +} + +Ref GreyscaleLuminanceSource::getByteMatrix() const { + return Ref(new ByteMatrix(getWidth(), getHeight(), getMatrix())); +} diff --git a/modules/wechat_qrcode/src/zxing/common/greyscale_luminance_source.hpp b/modules/wechat_qrcode/src/zxing/common/greyscale_luminance_source.hpp new file mode 100644 index 0000000..438b14e --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/greyscale_luminance_source.hpp @@ -0,0 +1,44 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_GREYSCALE_LUMINANCE_SOURCE_HPP__ +#define __ZXING_COMMON_GREYSCALE_LUMINANCE_SOURCE_HPP__ + +#include "../errorhandler.hpp" +#include "../luminance_source.hpp" +#include "bytematrix.hpp" + +namespace zxing { + +class GreyscaleLuminanceSource : public LuminanceSource { +private: + typedef LuminanceSource Super; + ArrayRef greyData_; + const int dataWidth_; + const int dataHeight_; + const int left_; + const int top_; + +public: + GreyscaleLuminanceSource(ArrayRef greyData, int dataWidth, int dataHeight, int left, + int top, int width, int height, ErrorHandler& err_handler); + + ArrayRef getRow(int y, ArrayRef row, ErrorHandler& err_handler) const override; + ArrayRef getMatrix() const override; + Ref getByteMatrix() const override; + + bool isRotateSupported() const override { return true; } + + Ref rotateCounterClockwise(ErrorHandler& err_handler) const override; +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_GREYSCALE_LUMINANCE_SOURCE_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/greyscale_rotated_luminance_source.cpp b/modules/wechat_qrcode/src/zxing/common/greyscale_rotated_luminance_source.cpp new file mode 100644 index 0000000..4f9e626 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/greyscale_rotated_luminance_source.cpp @@ -0,0 +1,67 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "greyscale_rotated_luminance_source.hpp" +#include "bytematrix.hpp" +using zxing::ArrayRef; +using zxing::ByteMatrix; +using zxing::ErrorHandler; +using zxing::GreyscaleRotatedLuminanceSource; +using zxing::Ref; + +// Note that dataWidth and dataHeight are not reversed, as we need to +// be able to traverse the greyData correctly, which does not get +// rotated. +GreyscaleRotatedLuminanceSource::GreyscaleRotatedLuminanceSource(ArrayRef greyData, + int dataWidth, int dataHeight, + int left, int top, int _width, + int _height, + ErrorHandler& err_handler) + : Super(_width, _height), greyData_(greyData), dataWidth_(dataWidth), left_(left), top_(top) { + // Intentionally comparing to the opposite dimension since we're rotated. + if (left + _width > dataHeight || top + _height > dataWidth) { + err_handler = IllegalArgumentErrorHandler("Crop rectangle does not fit within image data."); + } +} + +// The API asks for rows, but we're rotated, so we return columns. +ArrayRef GreyscaleRotatedLuminanceSource::getRow(int y, ArrayRef row, + ErrorHandler& err_handler) const { + if (y < 0 || y >= getHeight()) { + err_handler = IllegalArgumentErrorHandler("Requested row is outside the image."); + return ArrayRef(); + } + if (!row || row->size() < getWidth()) { + row = ArrayRef(getWidth()); + } + int offset = (left_ * dataWidth_) + (dataWidth_ - 1 - (y + top_)); + for (int x = 0; x < getWidth(); x++) { + row[x] = greyData_[offset]; + offset += dataWidth_; + } + return row; +} + +ArrayRef GreyscaleRotatedLuminanceSource::getMatrix() const { + ArrayRef result(getWidth() * getHeight()); + for (int y = 0; y < getHeight(); y++) { + char* row = &result[y * getWidth()]; + int offset = (left_ * dataWidth_) + (dataWidth_ - 1 - (y + top_)); + for (int x = 0; x < getWidth(); x++) { + row[x] = greyData_[offset]; + offset += dataWidth_; + } + } + return result; +} + +Ref GreyscaleRotatedLuminanceSource::getByteMatrix() const { + return Ref(new ByteMatrix(getWidth(), getHeight(), getMatrix())); +} diff --git a/modules/wechat_qrcode/src/zxing/common/greyscale_rotated_luminance_source.hpp b/modules/wechat_qrcode/src/zxing/common/greyscale_rotated_luminance_source.hpp new file mode 100644 index 0000000..2e1c8f6 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/greyscale_rotated_luminance_source.hpp @@ -0,0 +1,39 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_GREYSCALE_ROTATED_LUMINANCE_SOURCE_HPP__ +#define __ZXING_COMMON_GREYSCALE_ROTATED_LUMINANCE_SOURCE_HPP__ + +#include "../errorhandler.hpp" +#include "../luminance_source.hpp" +#include "bytematrix.hpp" +namespace zxing { + +class GreyscaleRotatedLuminanceSource : public LuminanceSource { +private: + typedef LuminanceSource Super; + ArrayRef greyData_; + const int dataWidth_; + const int left_; + const int top_; + +public: + GreyscaleRotatedLuminanceSource(ArrayRef greyData, int dataWidth, int dataHeight, + int left, int top, int _width, int _height, + ErrorHandler& err_handler); + + ArrayRef getRow(int y, ArrayRef row, ErrorHandler& err_handler) const override; + ArrayRef getMatrix() const override; + Ref getByteMatrix() const override; +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_GREYSCALE_ROTATED_LUMINANCE_SOURCE_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/grid_sampler.cpp b/modules/wechat_qrcode/src/zxing/common/grid_sampler.cpp new file mode 100644 index 0000000..52564f2 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/grid_sampler.cpp @@ -0,0 +1,119 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "grid_sampler.hpp" +#include "perspective_transform.hpp" +#include + +namespace zxing { + +GridSampler GridSampler::gridSampler; + +GridSampler::GridSampler() {} + +// Samples an image for a rectangular matrix of bits of the given dimension. +Ref GridSampler::sampleGrid(Ref image, int dimension, + Ref transform, + ErrorHandler &err_handler) { + Ref bits(new BitMatrix(dimension, err_handler)); + if (err_handler.ErrCode()) return Ref(); + + vector points(dimension << 1, 0.0f); + + int outlier = 0; + int maxOutlier = dimension * dimension * 3 / 10 - 1; + + for (int y = 0; y < dimension; y++) { + int max = points.size(); + float yValue = (float)y + 0.5f; + for (int x = 0; x < max; x += 2) { + points[x] = (float)(x >> 1) + 0.5f; + points[x + 1] = yValue; + } + transform->transformPoints(points); + // Quick check to see if points transformed to something inside the + // image; sufficient to check the endpoings + outlier += checkAndNudgePoints(image->getWidth(), image->getHeight(), points, err_handler); + if (err_handler.ErrCode()) return Ref(); + + if (outlier >= maxOutlier) { + ostringstream s; + s << "Over 30% points out of bounds."; + err_handler = ReaderErrorHandler(s.str().c_str()); + return Ref(); + } + + for (int x = 0; x < max; x += 2) { + if (image->get((int)points[x], (int)points[x + 1])) { + // Black (-ish) pixel + bits->set(x >> 1, y); + } + } + } + return bits; +} + +int GridSampler::checkAndNudgePoints(int width, int height, vector &points, + ErrorHandler &err_handler) { + // Modified to support stlport + float *pts = NULL; + + if (points.size() > 0) { + pts = &points[0]; + } else { + err_handler = ReaderErrorHandler("checkAndNudgePoints:: no points!"); + return -1; + } + + int size = (int)points.size() / 2; + + // The Java code assumes that if the start and end points are in bounds, the + // rest will also be. However, in some unusual cases points in the middle + // may also be out of bounds. Since we can't rely on an + // ArrayIndexOutOfBoundsException like Java, we check every point. + + int outCount = 0; + // int maxError = (int)(size/2/3 - 1); + + float maxborder = width / size * 3; + + for (size_t offset = 0; offset < points.size(); offset += 2) { + int x = (int)pts[offset]; + int y = (int)pts[offset + 1]; + // if((int)offset==0) + // cout<<"checkAndNudgePoints "<<(int)offset<<": ("< width || y < -1 || y > height) { + outCount++; + if (x > width + maxborder || y > height + maxborder || x < -maxborder || + y < -maxborder) { + err_handler = ReaderErrorHandler("checkAndNudgePoints::Out of bounds!"); + return -1; + } + } + + if (x <= -1) { + points[offset] = 0.0f; + } else if (x >= width) { + points[offset] = float(width - 1); + } + if (y <= -1) { + points[offset + 1] = 0.0f; + } else if (y >= height) { + points[offset + 1] = float(height - 1); + } + } + + return outCount; +} + +GridSampler &GridSampler::getInstance() { return gridSampler; } +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/common/grid_sampler.hpp b/modules/wechat_qrcode/src/zxing/common/grid_sampler.hpp new file mode 100644 index 0000000..c697d1c --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/grid_sampler.hpp @@ -0,0 +1,34 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_GRID_SAMPLER_HPP__ +#define __ZXING_COMMON_GRID_SAMPLER_HPP__ + +#include "bitmatrix.hpp" +#include "bytematrix.hpp" +#include "counted.hpp" +#include "perspective_transform.hpp" + +namespace zxing { +class GridSampler { +private: + static GridSampler gridSampler; + GridSampler(); + +public: + Ref sampleGrid(Ref image, int dimension, + Ref transform, ErrorHandler &err_handler); + static int checkAndNudgePoints(int width, int height, vector &points, + ErrorHandler &err_handler); + static GridSampler &getInstance(); +}; +} // namespace zxing + +#endif // __ZXING_COMMON_GRID_SAMPLER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/imagecut.cpp b/modules/wechat_qrcode/src/zxing/common/imagecut.cpp new file mode 100644 index 0000000..86fdf7f --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/imagecut.cpp @@ -0,0 +1,66 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "../../precomp.hpp" +#include "imagecut.hpp" + + +namespace zxing { + +ImageCut::ImageCut() {} + +ImageCut::~ImageCut() {} + +int ImageCut::Cut(uint8_t* poImageData, int iWidth, int iHeight, int iTopLeftX, int iTopLeftY, + int iBottomRightX, int iBottomRightY, ImageCutResult& result) { + if (iTopLeftX < 0 || iTopLeftX > iBottomRightX || iBottomRightX >= iWidth) return -1; + if (iTopLeftY < 0 || iTopLeftY > iBottomRightY || iBottomRightY >= iHeight) return -1; + int iNewWidth = iBottomRightX - iTopLeftX + 1; + int iNewHeight = iBottomRightY - iTopLeftY + 1; + + result.arrImage = new Array(iNewWidth * iNewHeight); + result.iHeight = iNewHeight; + result.iWidth = iNewWidth; + + int idx = 0; + for (int y = 0; y < iHeight; ++y) { + if (y < iTopLeftY || y > iBottomRightY) continue; + for (int x = 0; x < iWidth; ++x) { + if (x < iTopLeftX || x > iBottomRightX) continue; + result.arrImage[idx++] = poImageData[y * iWidth + x]; + } + } + return 0; +} + +int ImageCut::Cut(Ref matrix, float fRatio, ImageCutResult& result) { + int iWidth = matrix->getWidth(); + int iHeight = matrix->getHeight(); + + int iMinX = iWidth * (1 - fRatio) / 2; + int iMinY = iHeight * (1 - fRatio) / 2; + int iMaxX = iWidth * (1 + fRatio) / 2 - 1; + int iMaxY = iHeight * (1 + fRatio) / 2 - 1; + + if (iMinY < 0 || iMinY > iMaxX || iMaxX >= iWidth) return -1; + if (iMinX < 0 || iMinX > iMaxX || iMaxX >= iWidth) return -1; + int iNewHeight = iMaxY - iMinY + 1; + int iNewWidth = iMaxX - iMinX + 1; + + result.arrImage = new Array(iNewWidth * iNewHeight); + result.iWidth = iNewWidth; + result.iHeight = iNewHeight; + + int idx = 0; + for (int y = 0; y < iNewHeight; ++y) { + for (int x = 0; x < iNewWidth; ++x) { + result.arrImage[idx++] = matrix->get(x + iMinX, y + iMinY); + } + } + return 0; +} + +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/common/imagecut.hpp b/modules/wechat_qrcode/src/zxing/common/imagecut.hpp new file mode 100644 index 0000000..377191f --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/imagecut.hpp @@ -0,0 +1,32 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __ZXING_COMMON_IMAGECUT_HPP__ +#define __ZXING_COMMON_IMAGECUT_HPP__ +#include "bytematrix.hpp" +#include "counted.hpp" + +namespace zxing { + +typedef struct _ImageCutResult { + ArrayRef arrImage; + int iWidth; + int iHeight; +} ImageCutResult; + +class ImageCut { +public: + ImageCut(); + ~ImageCut(); + + static int Cut(uint8_t* poImageData, int iWidth, int iHeight, int iTopLeftX, int iTopLeftY, + int iBottomRightX, int iBottomRightY, ImageCutResult& result); + static int Cut(Ref matrix, float fRatio, ImageCutResult& result); +}; + +} // namespace zxing +#endif // __ZXING_COMMON_IMAGECUT_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/kmeans.cpp b/modules/wechat_qrcode/src/zxing/common/kmeans.cpp new file mode 100644 index 0000000..2c55edc --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/kmeans.cpp @@ -0,0 +1,89 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "../../precomp.hpp" +#include "kmeans.hpp" + +typedef unsigned int uint; + +namespace zxing { + +double cal_distance(vector a, vector b) { + const float KMEANS_COUNT_FACTOR = 0; + const float KMEANS_MS_FACTOR = 1; + + uint da = a.size(); + double val = 0.0; + for (uint i = 0; i < da; i++) { + if (i == 1) + val += KMEANS_MS_FACTOR * pow((a[i] - b[i]), 2); + else if (i == 0) + val += KMEANS_COUNT_FACTOR * pow((a[i] - b[i]), 2); + else + val += pow((a[i] - b[i]), 2); + } + return pow(val, 0.5); +} + +/* + * maxepoches max iteration epochs + * minchanged min central change times + */ +vector k_means(vector > trainX, uint k, uint maxepoches, uint minchanged) { + const uint row_num = trainX.size(); + const uint col_num = trainX[0].size(); + + // initialize the cluster central + vector clusters(k); + int step = trainX.size() / k; + + for (uint i = 0; i < k; i++) { + clusters[i].centroid = trainX[i * step]; + } + + // try max epochs times iteration untill convergence + for (uint it = 0; it < maxepoches; it++) { + for (uint i = 0; i < k; i++) { + clusters[i].samples.clear(); + } + for (uint j = 0; j < row_num; j++) { + uint c = 0; + double min_distance = cal_distance(trainX[j], clusters[c].centroid); + for (uint i = 1; i < k; i++) { + double distance = cal_distance(trainX[j], clusters[i].centroid); + if (distance < min_distance) { + min_distance = distance; + c = i; + } + } + clusters[c].samples.push_back(j); + } + + uint changed = 0; + // update cluster central + for (uint i = 0; i < k; i++) { + vector val(col_num, 0.0); + for (uint j = 0; j < clusters[i].samples.size(); j++) { + uint sample = clusters[i].samples[j]; + for (uint d = 0; d < col_num; d++) { + val[d] += trainX[sample][d]; + if (j == clusters[i].samples.size() - 1) { + double value = val[d] / clusters[i].samples.size(); + if (clusters[i].centroid[d] != value) { + clusters[i].centroid[d] = value; + changed++; + } + } + } + } + } + + if (changed <= minchanged) return clusters; + } + return clusters; +} + +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/common/kmeans.hpp b/modules/wechat_qrcode/src/zxing/common/kmeans.hpp new file mode 100644 index 0000000..5d656df --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/kmeans.hpp @@ -0,0 +1,26 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __ZXING_COMMON_KMEANS_HPP__ +#define __ZXING_COMMON_KMEANS_HPP__ +#include + +namespace zxing { + +using namespace std; +typedef unsigned int uint; + +struct Cluster { + vector centroid; + vector samples; +}; + +double cal_distance(vector a, vector b); +vector k_means(vector > trainX, uint k, uint maxepoches, uint minchanged); + +} // namespace zxing +#endif // __ZXING_COMMON_KMEANS_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/mathutils.hpp b/modules/wechat_qrcode/src/zxing/common/mathutils.hpp new file mode 100644 index 0000000..bd46622 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/mathutils.hpp @@ -0,0 +1,95 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_MATHUTILS_HPP__ +#define __ZXING_COMMON_MATHUTILS_HPP__ + +#include +#if (defined __GNUC__ && defined __x86_64__ && defined __SSE2__ && !defined __APPLE__ && \ + !defined __GXX_WEAK__) +#include +#elif defined _MSC_VER && (defined _M_X64 || defined _M_IX86) +#include +#endif + +#include +#include +#include + +namespace zxing { +namespace common { + +class MathUtils { +private: + MathUtils(); + ~MathUtils(); + +public: + static inline float distance(float aX, float aY, float bX, float bY) { + float xDiff = aX - bX; + float yDiff = aY - bY; + return sqrt(float(xDiff * xDiff + yDiff * yDiff)); + } + + static inline float distance_4_int(int aX, int aY, int bX, int bY) { + return sqrt(float((aX - bX) * (aX - bX) + (aY - bY) * (aY - bY))); + } + + static inline void getRangeValues(int& minValue, int& maxValue, int min, int max) { + int finalMinValue, finalMaxValue; + + if (minValue < maxValue) { + finalMinValue = minValue; + finalMaxValue = maxValue; + } else { + finalMinValue = maxValue; + finalMaxValue = minValue; + } + + finalMinValue = finalMinValue > min ? finalMinValue : min; + finalMaxValue = finalMaxValue < max ? finalMaxValue : max; + + minValue = finalMinValue; + maxValue = finalMaxValue; + } + + static inline bool isInRange(float x, float y, float width, float height) { + if ((x >= 0.0 && x <= (width - 1.0)) && (y >= 0.0 && y <= (height - 1.0))) { + return true; + } else { + return false; + } + } + + static inline float distance(int aX, int aY, int bX, int bY) { + int xDiff = aX - bX; + int yDiff = aY - bY; + return sqrt(float(xDiff * xDiff + yDiff * yDiff)); + } + + static inline float VecCross(float* v1, float* v2) { return v1[0] * v2[1] - v1[1] * v2[0]; } + + static inline void Stddev(std::vector& resultSet, float& avg, float& stddev) { + double sum = std::accumulate(resultSet.begin(), resultSet.end(), 0.0); + avg = sum / resultSet.size(); + + double accum = 0.0; + for (std::size_t i = 0; i < resultSet.size(); i++) { + accum += (resultSet[i] - avg) * (resultSet[i] - avg); + } + + stddev = sqrt(accum / (resultSet.size())); + } +}; + +} // namespace common +} // namespace zxing + +#endif // __ZXING_COMMON_MATHUTILS_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/perspective_transform.cpp b/modules/wechat_qrcode/src/zxing/common/perspective_transform.cpp new file mode 100644 index 0000000..74ac2cd --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/perspective_transform.cpp @@ -0,0 +1,120 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "perspective_transform.hpp" + + +namespace zxing { + +// 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 +PerspectiveTransform::PerspectiveTransform(float inA11, float inA21, float inA31, float inA12, + float inA22, float inA32, float inA13, float inA23, + float inA33) + : a11(inA11), + a12(inA12), + a13(inA13), + a21(inA21), + a22(inA22), + a23(inA23), + a31(inA31), + a32(inA32), + a33(inA33) {} + +Ref 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) { + Ref qToS = + PerspectiveTransform::quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3); + Ref sToQ = + PerspectiveTransform::squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); + return sToQ->times(qToS); +} + +Ref 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 (fabs(dx3) <= 1e-6 && fabs(dy3) <= 1e-6) { + Ref result( + new PerspectiveTransform(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0.0f, 0.0f, 1.0f)); + return result; + } 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; + Ref result( + new PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0, y1 - y0 + a13 * y1, + y3 - y0 + a23 * y3, y0, a13, a23, 1.0f)); + return result; + } +} + +Ref 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(); +} + +Ref PerspectiveTransform::buildAdjoint() { + // Adjoint is the transpose of the cofactor matrix: + Ref result(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)); + return result; +} + +Ref PerspectiveTransform::times(Ref other) { + Ref result( + 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)); + return result; +} + +void PerspectiveTransform::transformPoints(vector& points) { + int max = points.size(); + + // Modified to support stlport + float* pts = NULL; + + if (points.size() > 0) { + pts = &points[0]; + } + + for (int i = 0; i < max; i += 2) { + float x = pts[i]; + float y = pts[i + 1]; + + float denominator = a13 * x + a23 * y + a33; + + float w = 1.0f / denominator; + + pts[i] = (a11 * x + a21 * y + a31) * w; + pts[i + 1] = (a12 * x + a22 * y + a32) * w; + } +} + +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/common/perspective_transform.hpp b/modules/wechat_qrcode/src/zxing/common/perspective_transform.hpp new file mode 100644 index 0000000..59e1db6 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/perspective_transform.hpp @@ -0,0 +1,39 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_PERSPECTIVETRANSFORM_HPP__ +#define __ZXING_COMMON_PERSPECTIVETRANSFORM_HPP__ + +#include "counted.hpp" + +#include + +namespace zxing { +class PerspectiveTransform : public Counted { +private: + float a11, a12, a13, a21, a22, a23, a31, a32, a33; + PerspectiveTransform(float a11, float a21, float a31, float a12, float a22, float a32, + float a13, float a23, float a33); + +public: + static Ref 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); + static Ref squareToQuadrilateral(float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3); + static Ref quadrilateralToSquare(float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3); + Ref buildAdjoint(); + Ref times(Ref other); + void transformPoints(std::vector& points); +}; +} // namespace zxing + +#endif // __ZXING_COMMON_PERSPECTIVETRANSFORM_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgf.cpp b/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgf.cpp new file mode 100644 index 0000000..8e3ad18 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgf.cpp @@ -0,0 +1,99 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "genericgf.hpp" +#include "genericgfpoly.hpp" + +using zxing::ErrorHandler; +using zxing::GenericGF; +using zxing::GenericGFPoly; +using zxing::Ref; + +GenericGF::GenericGF(int primitive_, int size_, int b, ErrorHandler &err_handler) + : size(size_), primitive(primitive_), generatorBase(b) { + expTable.resize(size); + logTable.resize(size); + + int x = 1; + + for (int i = 0; i < size; i++) { + expTable[i] = x; + x <<= 1; // 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 = + Ref(new GenericGFPoly(*this, ArrayRef(new Array(1)), err_handler)); + zero->getCoefficients()[0] = 0; + one = + Ref(new GenericGFPoly(*this, ArrayRef(new Array(1)), err_handler)); + one->getCoefficients()[0] = 1; + if (err_handler.ErrCode()) return; + // initialized = true; +} + +Ref GenericGF::getZero() { return zero; } + +Ref GenericGF::getOne() { return one; } + +Ref GenericGF::buildMonomial(int degree, int coefficient, + ErrorHandler &err_handler) { + if (degree < 0) { + err_handler = IllegalArgumentErrorHandler("Degree must be non-negative"); + return Ref(); + } + if (coefficient == 0) { + return zero; + } + ArrayRef coefficients(new Array(degree + 1)); + coefficients[0] = coefficient; + + Ref gfpoly(new GenericGFPoly(*this, coefficients, err_handler)); + if (err_handler.ErrCode()) return Ref(); + return gfpoly; +} + +int GenericGF::addOrSubtract(int a, int b) { return a ^ b; } + +int GenericGF::exp(int a) { return expTable[a]; } + +int GenericGF::log(int a, ErrorHandler &err_handler) { + if (a == 0) { + err_handler = IllegalArgumentErrorHandler("cannot give log(0)"); + return -1; + } + return logTable[a]; +} + +int GenericGF::inverse(int a, ErrorHandler &err_handler) { + if (a == 0) { + err_handler = IllegalArgumentErrorHandler("Cannot calculate the inverse of 0"); + return -1; + } + return expTable[size - logTable[a] - 1]; +} + +int GenericGF::multiply(int a, int b) { + if (a == 0 || b == 0) { + return 0; + } + + return expTable[(logTable[a] + logTable[b]) % (size - 1)]; +} + +int GenericGF::getSize() { return size; } + +int GenericGF::getGeneratorBase() { return generatorBase; } diff --git a/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgf.hpp b/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgf.hpp new file mode 100644 index 0000000..aabbeaf --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgf.hpp @@ -0,0 +1,58 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_REEDSOLOMON_GENERICGF_HPP__ +#define __ZXING_COMMON_REEDSOLOMON_GENERICGF_HPP__ + +#include "../../errorhandler.hpp" +#include "../counted.hpp" + +namespace zxing { +class GenericGFPoly; + +static zxing::ErrorHandler gf_err_handler_; +#define GF_AZTEC_DATA_12 (new GenericGF(0x1069, 4096, 1, gf_err_handler_)) +#define GF_AZTEC_DATA_10 (new GenericGF(0x409, 1024, 1, gf_err_handler_)) +#define GF_AZTEC_DATA_6 (new GenericGF(0x43, 64, 1, gf_err_handler_)) +#define GF_AZTEC_PARAM (new GenericGF(0x13, 16, 1, gf_err_handler_)) +#define GF_QR_CODE_FIELD_256 (new GenericGF(0x011D, 256, 0, gf_err_handler_)) +#define GF_DATA_MATRIX_FIELD_256 (new GenericGF(0x012D, 256, 1, gf_err_handler_)) +#define GF_AZTEC_DATA_8 (new GenericGF(0x012D, 256, 1, gf_err_handler_)) +#define GF_MAXICODE_FIELD_64 (new GenericGF(0x43, 64, 1, gf_err_handler_)) +// #define GF_WXCODE (new GenericGF(0x011D, 256, 0, gf_err_handler_)) + +class GenericGF : public Counted { +private: + std::vector expTable; + std::vector logTable; + Ref zero; + Ref one; + int size; + int primitive; + int generatorBase; + +public: + GenericGF(int primitive, int size, int b, ErrorHandler &err_handler); + + Ref getZero(); + Ref getOne(); + int getSize(); + int getGeneratorBase(); + Ref buildMonomial(int degree, int coefficient, ErrorHandler &err_handler); + + static int addOrSubtract(int a, int b); + int exp(int a); + int log(int a, ErrorHandler &err_handler); + int inverse(int a, ErrorHandler &err_handler); + int multiply(int a, int b); +}; +} // namespace zxing + +#endif // __ZXING_COMMON_REEDSOLOMON_GENERICGF_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgfpoly.cpp b/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgfpoly.cpp new file mode 100644 index 0000000..4098a37 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgfpoly.cpp @@ -0,0 +1,231 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "genericgfpoly.hpp" +#include "genericgf.hpp" + +using zxing::ArrayRef; +using zxing::ErrorHandler; +using zxing::GenericGFPoly; +using zxing::Ref; + +// VC++ +using zxing::GenericGF; + +GenericGFPoly::GenericGFPoly(GenericGF &field, ArrayRef coefficients, + ErrorHandler &err_handler) + : field_(field) { + if (coefficients->size() == 0) { + err_handler = IllegalArgumentErrorHandler("need coefficients"); + return; + } + int coefficientsLength = coefficients->size(); + 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) { + coefficients_ = field.getZero()->getCoefficients(); + } else { + coefficients_ = ArrayRef(new Array(coefficientsLength - firstNonZero)); + for (int i = 0; i < (int)coefficients_->size(); i++) { + coefficients_[i] = coefficients[i + firstNonZero]; + } + } + } else { + coefficients_ = coefficients; + } +} + +ArrayRef GenericGFPoly::getCoefficients() { return coefficients_; } + +int GenericGFPoly::getDegree() { return coefficients_->size() - 1; } + +bool GenericGFPoly::isZero() { return coefficients_[0] == 0; } + +int GenericGFPoly::getCoefficient(int degree) { + return coefficients_[coefficients_->size() - 1 - degree]; +} + +int GenericGFPoly::evaluateAt(int a) { + if (a == 0) { + // Just return the x^0 coefficient + return getCoefficient(0); + } + + int size = coefficients_->size(); + if (a == 1) { + // Just the sum of the coefficients + int result = 0; + for (int i = 0; i < size; i++) { + result = GenericGF::addOrSubtract(result, coefficients_[i]); + } + return result; + } + int result = coefficients_[0]; + for (int i = 1; i < size; i++) { + result = GenericGF::addOrSubtract(field_.multiply(a, result), coefficients_[i]); + } + return result; +} + +Ref GenericGFPoly::addOrSubtract(Ref other, + ErrorHandler &err_handler) { + if (!(&field_ == &other->field_)) { + err_handler = + IllegalArgumentErrorHandler("GenericGFPolys do not have same GenericGF field"); + return Ref(); + } + if (isZero()) { + return other; + } + if (other->isZero()) { + return Ref(this); + } + + ArrayRef smallerCoefficients = coefficients_; + ArrayRef largerCoefficients = other->getCoefficients(); + if (smallerCoefficients->size() > largerCoefficients->size()) { + ArrayRef temp = smallerCoefficients; + smallerCoefficients = largerCoefficients; + largerCoefficients = temp; + } + + ArrayRef sumDiff(new Array(largerCoefficients->size())); + int lengthDiff = largerCoefficients->size() - smallerCoefficients->size(); + // Copy high-order terms only found in higher-degree polynomial's + // coefficients + for (int i = 0; i < lengthDiff; i++) { + sumDiff[i] = largerCoefficients[i]; + } + + for (int i = lengthDiff; i < (int)largerCoefficients->size(); i++) { + sumDiff[i] = + GenericGF::addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); + } + + // return Ref(new GenericGFPoly(field_, sumDiff)); + Ref gfpoly(new GenericGFPoly(field_, sumDiff, err_handler)); + if (err_handler.ErrCode()) return Ref(); + return gfpoly; +} + +Ref GenericGFPoly::multiply(Ref other, + ErrorHandler &err_handler) { + if (!(&field_ == &other->field_)) { + err_handler = + IllegalArgumentErrorHandler("GenericGFPolys do not have same GenericGF field"); + return Ref(); + } + + if (isZero() || other->isZero()) { + return field_.getZero(); + } + + ArrayRef aCoefficients = coefficients_; + int aLength = aCoefficients->size(); + + ArrayRef bCoefficients = other->getCoefficients(); + int bLength = bCoefficients->size(); + + ArrayRef product(new Array(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 Ref(new GenericGFPoly(field_, product)); + Ref gfpoly(new GenericGFPoly(field_, product, err_handler)); + if (err_handler.ErrCode()) return Ref(); + return gfpoly; +} + +Ref GenericGFPoly::multiply(int scalar, ErrorHandler &err_handler) { + if (scalar == 0) { + return field_.getZero(); + } + if (scalar == 1) { + return Ref(this); + } + int size = coefficients_->size(); + ArrayRef product(new Array(size)); + for (int i = 0; i < size; i++) { + product[i] = field_.multiply(coefficients_[i], scalar); + } + // return Ref(new GenericGFPoly(field_, product)); + Ref gfpoly(new GenericGFPoly(field_, product, err_handler)); + if (err_handler.ErrCode()) return Ref(); + return gfpoly; +} + +Ref GenericGFPoly::multiplyByMonomial(int degree, int coefficient, + ErrorHandler &err_handler) { + if (degree < 0) { + err_handler = IllegalArgumentErrorHandler("degree must not be less then 0"); + return Ref(); + } + if (coefficient == 0) { + return field_.getZero(); + } + int size = coefficients_->size(); + ArrayRef product(new Array(size + degree)); + for (int i = 0; i < size; i++) { + product[i] = field_.multiply(coefficients_[i], coefficient); + } + // return Ref(new GenericGFPoly(field_, product)); + Ref gfpoly(new GenericGFPoly(field_, product, err_handler)); + if (err_handler.ErrCode()) return Ref(); + return gfpoly; +} + +std::vector> GenericGFPoly::divide(Ref other, + ErrorHandler &err_handler) { + if (!(&field_ == &other->field_)) { + err_handler = + IllegalArgumentErrorHandler("GenericGFPolys do not have same GenericGF field"); + return std::vector>(); + } + if (other->isZero()) { + err_handler = IllegalArgumentErrorHandler("divide by 0"); + return std::vector>(); + } + + Ref quotient = field_.getZero(); + Ref remainder = Ref(this); + + int denominatorLeadingTerm = other->getCoefficient(other->getDegree()); + int inverseDenominatorLeadingTerm = field_.inverse(denominatorLeadingTerm, err_handler); + if (err_handler.ErrCode()) return std::vector>(); + + while (remainder->getDegree() >= other->getDegree() && !remainder->isZero()) { + int degreeDifference = remainder->getDegree() - other->getDegree(); + int scale = field_.multiply(remainder->getCoefficient(remainder->getDegree()), + inverseDenominatorLeadingTerm); + Ref term = other->multiplyByMonomial(degreeDifference, scale, err_handler); + if (err_handler.ErrCode()) return std::vector>(); + Ref iterationQuotiont = + field_.buildMonomial(degreeDifference, scale, err_handler); + if (err_handler.ErrCode()) return std::vector>(); + quotient = quotient->addOrSubtract(iterationQuotiont, err_handler); + remainder = remainder->addOrSubtract(term, err_handler); + if (err_handler.ErrCode()) return std::vector>(); + } + + std::vector> returnValue(2); + returnValue[0] = quotient; + returnValue[1] = remainder; + return returnValue; +} diff --git a/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgfpoly.hpp b/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgfpoly.hpp new file mode 100644 index 0000000..031c0b7 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/reedsolomon/genericgfpoly.hpp @@ -0,0 +1,43 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_REEDSOLOMON_GENERICGFPOLY_HPP__ +#define __ZXING_COMMON_REEDSOLOMON_GENERICGFPOLY_HPP__ + +#include "../../errorhandler.hpp" +#include "../array.hpp" +#include "../counted.hpp" + +namespace zxing { + +class GenericGF; + +class GenericGFPoly : public Counted { +private: + GenericGF &field_; + ArrayRef coefficients_; + +public: + GenericGFPoly(GenericGF &field, ArrayRef coefficients, ErrorHandler &err_handler); + ArrayRef getCoefficients(); + int getDegree(); + bool isZero(); + int getCoefficient(int degree); + int evaluateAt(int a); + Ref addOrSubtract(Ref other, ErrorHandler &err_handler); + Ref multiply(Ref other, ErrorHandler &err_handler); + Ref multiply(int scalar, ErrorHandler &err_handler); + Ref multiplyByMonomial(int degree, int coefficient, ErrorHandler &err_handler); + std::vector> divide(Ref other, ErrorHandler &err_handler); +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_REEDSOLOMON_GENERICGFPOLY_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/reedsolomon/reed_solomon_decoder.cpp b/modules/wechat_qrcode/src/zxing/common/reedsolomon/reed_solomon_decoder.cpp new file mode 100644 index 0000000..fec9c82 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/reedsolomon/reed_solomon_decoder.cpp @@ -0,0 +1,185 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "reed_solomon_decoder.hpp" + +using zxing::ArrayRef; +using zxing::ErrorHandler; +using zxing::GenericGFPoly; +using zxing::ReedSolomonDecoder; +using zxing::Ref; +using zxing::GenericGF; + +ReedSolomonDecoder::ReedSolomonDecoder(Ref field_) : field(field_) {} + +ReedSolomonDecoder::~ReedSolomonDecoder() {} + +void ReedSolomonDecoder::decode(ArrayRef received, int twoS, ErrorHandler &err_handler) { + Ref poly(new GenericGFPoly(*field, received, err_handler)); + if (err_handler.ErrCode()) return; + ArrayRef syndromeCoefficients(twoS); + bool noError = true; + for (int i = 0; i < twoS; i++) { + int eval = poly->evaluateAt(field->exp(i + field->getGeneratorBase())); + syndromeCoefficients[syndromeCoefficients->size() - 1 - i] = eval; + if (eval != 0) { + noError = false; + } + } + if (noError) { + return; + } + Ref syndrome(new GenericGFPoly(*field, syndromeCoefficients, err_handler)); + Ref monomial = field->buildMonomial(twoS, 1, err_handler); + if (!monomial || err_handler.ErrCode()) { + err_handler = ErrorHandler("buildMonomial was zero"); + return; + } + vector> sigmaOmega = + runEuclideanAlgorithm(monomial, syndrome, twoS, err_handler); + if (err_handler.ErrCode()) return; + + Ref sigma = sigmaOmega[0]; + Ref omega = sigmaOmega[1]; + ArrayRef errorLocations = findErrorLocations(sigma, err_handler); + if (err_handler.ErrCode()) return; + + ArrayRef errorMagitudes = findErrorMagnitudes(omega, errorLocations, err_handler); + if (err_handler.ErrCode()) return; + for (int i = 0; i < errorLocations->size(); i++) { + int position = received->size() - 1 - field->log(errorLocations[i], err_handler); + if (position < 0 || err_handler.ErrCode()) { + err_handler = ErrorHandler("Bad error location"); + return; + } + received[position] = GenericGF::addOrSubtract(received[position], errorMagitudes[i]); + } +} + +vector> ReedSolomonDecoder::runEuclideanAlgorithm(Ref a, + Ref b, int R, + ErrorHandler &err_handler) { + vector> result(2); + + // Assume a's degree is >= b's + if (a->getDegree() < b->getDegree()) { + Ref tmp = a; + a = b; + b = tmp; + } + + Ref rLast(a); + Ref r(b); + Ref tLast(field->getZero()); + Ref t(field->getOne()); + + // Run Euclidean algorithm until r's degree is less than R/2 + while (r->getDegree() >= R / 2) { + Ref rLastLast(rLast); + Ref tLastLast(tLast); + rLast = r; + tLast = t; + + // Divide rLastLast by rLast, with quotient q and remainder r + if (rLast->isZero()) { + // Oops, Euclidean algorithm already terminated? + err_handler = ErrorHandler("r_{i-1} was zero"); + return vector>(); + } + r = rLastLast; + Ref q = field->getZero(); + int denominatorLeadingTerm = rLast->getCoefficient(rLast->getDegree()); + int dltInverse = field->inverse(denominatorLeadingTerm, err_handler); + if (err_handler.ErrCode()) return vector>(); + 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, err_handler), err_handler); + r = r->addOrSubtract(rLast->multiplyByMonomial(degreeDiff, scale, err_handler), + err_handler); + if (err_handler.ErrCode()) return vector>(); + } + + Ref tmp = q->multiply(tLast, err_handler); + if (err_handler.ErrCode()) return vector>(); + t = tmp->addOrSubtract(tLastLast, err_handler); + if (err_handler.ErrCode()) return vector>(); + + if (r->getDegree() >= rLast->getDegree()) { + err_handler = ErrorHandler("Division algorithm failed to reduce polynomial?"); + return vector>(); + } + } + + int sigmaTildeAtZero = t->getCoefficient(0); + if (sigmaTildeAtZero == 0) { + err_handler = ErrorHandler("sigmaTilde(0) was zero"); + return vector>(); + } + + int inverse = field->inverse(sigmaTildeAtZero, err_handler); + Ref sigma(t->multiply(inverse, err_handler)); + Ref omega(r->multiply(inverse, err_handler)); + if (err_handler.ErrCode()) return vector>(); + + result[0] = sigma; + result[1] = omega; + return result; +} + +ArrayRef ReedSolomonDecoder::findErrorLocations(Ref errorLocator, + ErrorHandler &err_handler) { + // This is a direct application of Chien's search + int numErrors = errorLocator->getDegree(); + if (numErrors == 1) { // shortcut + ArrayRef result(new Array(1)); + result[0] = errorLocator->getCoefficient(1); + return result; + } + ArrayRef result(new Array(numErrors)); + int e = 0; + for (int i = 1; i < field->getSize() && e < numErrors; i++) { + if (errorLocator->evaluateAt(i) == 0) { + result[e] = field->inverse(i, err_handler); + e++; + } + } + if (e != numErrors || err_handler.ErrCode()) { + err_handler = ErrorHandler("Error locator degree does not match number of root"); + return ArrayRef(); + } + return result; +} + +ArrayRef ReedSolomonDecoder::findErrorMagnitudes(Ref errorEvaluator, + ArrayRef errorLocations, + ErrorHandler &err_handler) { + // This is directly applying Forney's Formula + int s = errorLocations->size(); + ArrayRef result(new Array(s)); + for (int i = 0; i < s; i++) { + int xiInverse = field->inverse(errorLocations[i], err_handler); + int denominator = 1; + for (int j = 0; j < s; j++) { + if (i != j) { + 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, err_handler)); + if (field->getGeneratorBase() != 0) { + result[i] = field->multiply(result[i], xiInverse); + } + } + if (err_handler.ErrCode()) return ArrayRef(); + return result; +} diff --git a/modules/wechat_qrcode/src/zxing/common/reedsolomon/reed_solomon_decoder.hpp b/modules/wechat_qrcode/src/zxing/common/reedsolomon/reed_solomon_decoder.hpp new file mode 100644 index 0000000..15271dd --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/reedsolomon/reed_solomon_decoder.hpp @@ -0,0 +1,43 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_REEDSOLOMON_REEDSOLOMONDECODER_HPP__ +#define __ZXING_COMMON_REEDSOLOMON_REEDSOLOMONDECODER_HPP__ + +#include "../../errorhandler.hpp" +#include "../array.hpp" +#include "../counted.hpp" +#include "genericgf.hpp" +#include "genericgfpoly.hpp" + +namespace zxing { +class GenericGFPoly; +class GenericGF; + +class ReedSolomonDecoder { +private: + Ref field; + +public: + explicit ReedSolomonDecoder(Ref fld); + ~ReedSolomonDecoder(); + void decode(ArrayRef received, int twoS, ErrorHandler &err_handler); + std::vector> runEuclideanAlgorithm(Ref a, + Ref b, int R, + ErrorHandler &err_handler); + +private: + ArrayRef findErrorLocations(Ref errorLocator, ErrorHandler &err_handler); + ArrayRef findErrorMagnitudes(Ref errorEvaluator, + ArrayRef errorLocations, ErrorHandler &err_handler); +}; +} // namespace zxing + +#endif // __ZXING_COMMON_REEDSOLOMON_REEDSOLOMONDECODER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/str.cpp b/modules/wechat_qrcode/src/zxing/common/str.cpp new file mode 100644 index 0000000..1de3dd0 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/str.cpp @@ -0,0 +1,96 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "str.hpp" + +using zxing::Ref; +using zxing::String; +using zxing::StrUtil; +String::String(const std::string& text) : text_(text) {} + +String::String(int capacity) { text_.reserve(capacity); } + +const std::string& String::getText() const { return text_; } + +char String::charAt(int i) const { return text_[i]; } + +int String::size() const { return text_.size(); } + +int String::length() const { return text_.size(); } + +Ref String::substring(int i) const { return Ref(new String(text_.substr(i))); } + +Ref String::substring(int start, int end) const { + return Ref(new String(text_.substr(start, (end - start)))); +} + +void String::append(const std::string& tail) { text_.append(tail); } + +void String::append(char c) { text_.append(1, c); } + +void String::append(int d) { + string str = StrUtil::numberToString(d); + text_.append(str); +} + +void String::append(Ref str) { append(str->getText()); } + +string StrUtil::COMBINE_STRING(string str1, string str2) { + string str = str1; + str += str2; + return str; +} + +string StrUtil::COMBINE_STRING(string str1, char c) { + string str = str1; + str += c; + return str; +} + +string StrUtil::COMBINE_STRING(string str1, int d) { + string str = str1; + str += numberToString(d); + return str; +} + +Ref StrUtil::COMBINE_STRING(char c1, Ref content, char c2) { + Ref str(new String(0)); + str->append(c1); + str->append(content); + str->append(c2); + + return str; +} + +template +string StrUtil::numberToString(T Number) { + ostringstream ss; + ss << Number; + return ss.str(); +} + +template +T StrUtil::stringToNumber(const string& Text) { + std::istringstream ss(Text); + T result; + return ss >> result ? result : 0; +} + +int StrUtil::indexOf(const char* str, char c) { + int len = strlen(str); + + for (int i = 0; i < len; i++) { + if (str[i] == c) { + return i; + } + } + + return -1; +} diff --git a/modules/wechat_qrcode/src/zxing/common/str.hpp b/modules/wechat_qrcode/src/zxing/common/str.hpp new file mode 100644 index 0000000..3d20910 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/str.hpp @@ -0,0 +1,58 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_STR_HPP__ +#define __ZXING_COMMON_STR_HPP__ + +#include "counted.hpp" + +#include +#include +using std::string; +namespace zxing { + +class String : public Counted { +private: + std::string text_; + +public: + explicit String(const std::string& text); + explicit String(int); + char charAt(int) const; + Ref substring(int) const; + Ref substring(int, int) const; + const std::string& getText() const; + int size() const; + void append(std::string const& tail); + void append(char c); + void append(int d); + void append(Ref str); + int length() const; +}; + +class StrUtil { +public: + static string COMBINE_STRING(string str1, string str2); + static string COMBINE_STRING(string str1, char c); + static string COMBINE_STRING(string str1, int d); + static Ref COMBINE_STRING(char c1, Ref content, char c2); + + template + static string numberToString(T Number); + + template + static T stringToNumber(const string& Text); + + static int indexOf(const char* str, char c); +}; + +} // namespace zxing + +#endif // __ZXING_COMMON_STR_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/stringutils.cpp b/modules/wechat_qrcode/src/zxing/common/stringutils.cpp new file mode 100644 index 0000000..ed9fe3e --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/stringutils.cpp @@ -0,0 +1,683 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "../common/stringutils.hpp" +#include "../decodehints.hpp" + +using namespace zxing::common; + +// N.B.: these are the iconv strings for at least some versions of iconv + +char const* const StringUtils::PLATFORM_DEFAULT_ENCODING = "ANY"; +char const* const StringUtils::ASCII = "ASCII"; +char const* const StringUtils::SHIFT_JIS = "SHIFT-JIS"; +char const* const StringUtils::GBK = "GBK"; +char const* const StringUtils::EUC_JP = "EUC-JP"; +char const* const StringUtils::UTF8 = "UTF-8"; +char const* const StringUtils::ISO88591 = "ISO8859-1"; +char const* const StringUtils::GB2312 = "GB2312"; +char const* const StringUtils::BIG5 = "BIG5"; +char const* const StringUtils::GB18030 = "GB18030"; + +const bool StringUtils::ASSUME_SHIFT_JIS = false; + +#ifdef USE_UCHARDET +#include "uchardet/uchardet.h" +#endif + +// Added convertString +#ifndef NO_ICONV +#include + +// Required for compatibility. TODO: test on Symbian +//#ifdef ZXING_ICONV_CONST +#undef ICONV_CONST +#define ICONV_CONST const +//#endif + +#ifndef ICONV_CONST +#define ICONV_CONST /**/ +#endif + +// Add this to fix both Mac and Windows compilers +// by Skylook +template +class sloppy {}; + +// convert between T** and const T** +template +class sloppy { + T** t; + +public: + sloppy(T** mt) : t(mt) {} + sloppy(const T** mt) : t(const_cast(mt)) {} + + operator T* *() const { return t; } + operator const T* *() const { return const_cast(t); } +}; +#endif + +string StringUtils::convertString(const char* rawData, int length, const char* fromCharset, + const char* toCharset) { + string result; + const char* bufIn = rawData; + int nIn = length; + + // If from and to charset are the same, return + int ret = strcmp(fromCharset, toCharset); + if (ret == 0) { + result.append((const char*)bufIn, nIn); + return result; + } + +#ifndef NO_ICONV + if (nIn == 0) { + return ""; + } + iconv_t cd; + // cout< 0) { + // size_t oneway = iconv(cd, &fromPtr, &nFrom, &toPtr, &nTo); + oneway = iconv(cd, sloppy(&fromPtr), &nFrom, sloppy(&toPtr), &nTo); + } + iconv_close(cd); + + int nResult = maxOut - nTo; + bufOut[nResult] = '\0'; + result.append((const char*)bufOut); + delete[] bufOut; + + // Cannot convert string + if (oneway == (size_t)(-1)) { + // result.append((const char *)bufIn, nIn); + result = ""; + } +#else + result.append((const char*)bufIn, nIn); +#endif + + return result; +} + +string StringUtils::guessEncoding(char* bytes, int length) { +#ifdef USE_UCHARDET + if (length < 10) { + return guessEncodingZXing(bytes, length); + } else { + return guessEncodingUCharDet(bytes, length); + } +#else + return guessEncodingZXing(bytes, length); +#endif +} + +#ifdef USE_UCHARDET + +string StringUtils::guessEncodingUCharDet(char* bytes, int length) { + uchardet_t handle = uchardet_new(); + + int retval = uchardet_handle_data(handle, bytes, length); + + if (retval != 0) { + fprintf(stderr, "Handle data error.\n"); + exit(0); + } + + uchardet_data_end(handle); + + const char* charset = uchardet_get_charset(handle); + + string charsetStr(charset); + + uchardet_delete(handle); + + if (charsetStr.size() != 0) { + return charsetStr; + } else { + return guessEncodingZXing(bytes, length); + } + + // Otherwise, we take a wild guess with platform encoding + // return PLATFORM_DEFAULT_ENCODING; +} +#endif + +string StringUtils::guessEncodingZXing(char* bytes, int length) { + // + // typedef bool boolean; + // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS, + // which should be by far the most common encodings. + bool canBeISO88591 = true; + bool canBeShiftJIS = true; + bool canBeUTF8 = true; + bool canBeGB2312 = true; + bool canBeGBK = true; + bool canBeBIG5 = true; + bool canBeASCII = 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; + + int gb2312SCByteChars = 0; + int big5TWBytesChars = 0; + + bool utf8bom = length > 3 && (unsigned char)bytes[0] == 0xEF && + (unsigned char)bytes[1] == 0xBB && (unsigned char)bytes[2] == 0xBF; + + for (int i = 0; i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8 || canBeGBK); 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; + } + } + } + } + } + } + // 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; + } + } + + // ISO-8859-1 stuff + if (canBeISO88591) { + if (value > 0x7F && value < 0xA0) { + canBeISO88591 = false; + } else if (value > 0x9F) { + if (value < 0xC0 || value == 0xD7 || value == 0xF7) { + isoHighOther++; + } + } + } + } + + // Get how many chinese sc & tw words + gb2312SCByteChars = is_gb2312_code(bytes, length); + big5TWBytesChars = is_big5_code(bytes, length); + + if (gb2312SCByteChars <= 0) { + canBeGB2312 = false; + } + + if (big5TWBytesChars <= 0) { + canBeBIG5 = false; + } + + if (!is_gbk_code(bytes, length)) { + canBeGBK = false; + } + + if (canBeUTF8 && utf8BytesLeft > 0) { + canBeUTF8 = false; + } + if (canBeShiftJIS && sjisBytesLeft > 0) { + canBeShiftJIS = false; + } + + if (is_ascii_code(bytes, length) <= 0) { + canBeASCII = 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; + } + + // if ( canBeBIG5 == false && canBeGB2312 == false ) + int chineseWordLen = + gb2312SCByteChars > big5TWBytesChars ? gb2312SCByteChars : big5TWBytesChars; + int chineseByteLen = chineseWordLen * 2; + int japaneseByteLen = sjisMaxKatakanaWordLength + sjisMaxDoubleBytesWordLength * 2; + + // if ( chineseByteLen < japaneseByteLen || (japaneseByteLen == 0 && + // chineseByteLen == 0) ) if ( (gb2312SCByteChars < sjisKatakanaChars) && + // (big5TWBytesChars < sjisKatakanaChars) ) + //{ + // Easy -- if assuming Shift_JIS or at least 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; + if (chineseByteLen <= japaneseByteLen) { + if (chineseByteLen == japaneseByteLen) { + if (chineseWordLen < sjisKatakanaChars) { + return SHIFT_JIS; + } + } else { + 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) { + if ((sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || + isoHighOther * 10 >= length) { + /* + if ( chineseByteLen < japaneseByteLen ) + { + return SHIFT_JIS; + } + */ + if (chineseByteLen <= japaneseByteLen) { + if (chineseByteLen == japaneseByteLen) { + if (chineseWordLen < sjisKatakanaChars) { + return SHIFT_JIS; + } + } else { + return SHIFT_JIS; + } + } + } else { + if (chineseByteLen <= 0 && !canBeGB2312 && !canBeBIG5) { + return ISO88591; + } + } + } + //} + + // Otherwise, try in order ISO-8859-1, Shift JIS, UTF-8 and fall back to + // default platform encoding + if (canBeGB2312) { + return GB2312; + } + + if (canBeBIG5) { + return BIG5; + } + + if (canBeShiftJIS) { + return SHIFT_JIS; + } + + if (canBeGBK) { + return GBK; + } + + if (canBeISO88591) { + return ISO88591; + } + + if (canBeUTF8) { + return UTF8; + } + + if (canBeASCII) { + return ASCII; + } + + // Otherwise, we take a wild guess with platform encoding + return PLATFORM_DEFAULT_ENCODING; +} + +// judge the byte whether begin with binary 10 +int StringUtils::is_utf8_special_byte(unsigned char c) { + unsigned char special_byte = 0X02; // binary 00000010 + if (c >> 6 == special_byte) { + return 1; + } else { + return 0; + } +} + +int StringUtils::is_utf8_code(char* str, int length) { + unsigned char one_byte = 0X00; // binary 00000000 + unsigned char two_byte = 0X06; // binary 00000110 + unsigned char three_byte = 0X0E; // binary 00001110 + unsigned char four_byte = 0X1E; // binary 00011110 + unsigned char five_byte = 0X3E; // binary 00111110 + unsigned char six_byte = 0X7E; // binary 01111110 + + int utf8_yes = 0; + int utf8_no = 0; + + unsigned char k = 0; + unsigned char m = 0; + unsigned char n = 0; + unsigned char p = 0; + unsigned char q = 0; + + unsigned char c = 0; + for (int i = 0; i < length;) { + c = (unsigned char)str[i]; + if (c >> 7 == one_byte) { + i++; + continue; + } else if (c >> 5 == two_byte) { + k = (unsigned char)str[i + 1]; + if (is_utf8_special_byte(k)) { + utf8_yes++; + i += 2; + continue; + } + } else if (c >> 4 == three_byte) { + m = (unsigned char)str[i + 1]; + n = (unsigned char)str[i + 2]; + if (is_utf8_special_byte(m) && is_utf8_special_byte(n)) { + utf8_yes++; + i += 3; + continue; + } + } else if (c >> 3 == four_byte) { + k = (unsigned char)str[i + 1]; + m = (unsigned char)str[i + 2]; + n = (unsigned char)str[i + 3]; + if (is_utf8_special_byte(k) && is_utf8_special_byte(m) && is_utf8_special_byte(n)) { + utf8_yes++; + i += 4; + continue; + } + } else if (c >> 2 == five_byte) { + k = (unsigned char)str[i + 1]; + m = (unsigned char)str[i + 2]; + n = (unsigned char)str[i + 3]; + p = (unsigned char)str[i + 4]; + if (is_utf8_special_byte(k) && is_utf8_special_byte(m) && is_utf8_special_byte(n) && + is_utf8_special_byte(p)) { + utf8_yes++; + i += 5; + continue; + } + } else if (c >> 1 == six_byte) { + k = (unsigned char)str[i + 1]; + m = (unsigned char)str[i + 2]; + n = (unsigned char)str[i + 3]; + p = (unsigned char)str[i + 4]; + q = (unsigned char)str[i + 5]; + if (is_utf8_special_byte(k) && is_utf8_special_byte(m) && is_utf8_special_byte(n) && + is_utf8_special_byte(p) && is_utf8_special_byte(q)) { + utf8_yes++; + i += 6; + continue; + } + } + + utf8_no++; + i++; + } + + // printf("uft8_yes: %d utf8_no:%d\n", utf8_yes, utf8_no); + if ((utf8_yes + utf8_no) != 0) { + int ret = (100 * utf8_yes) / (utf8_yes + utf8_no); + if (ret > 90) { + return 1; + } else { + return 0; + } + } + return 0; +} +int StringUtils::is_gb2312_code(char* str, int length) { + unsigned char one_byte = 0X00; // binary 00000000 + + int gb2312_yes = 0; + int gb2312_no = 0; + + unsigned char k = 0; + + unsigned char c = 0; + for (int i = 0; i < length;) { + c = (unsigned char)str[i]; + if (c >> 7 == one_byte) { + i++; + continue; + } else if (c >= 0XA1 && c <= 0XF7) { + k = (unsigned char)str[i + 1]; + if (k >= 0XA1 && k <= 0XFE) { + gb2312_yes++; + i += 2; + continue; + } + } + + gb2312_no++; + i += 2; + } + + // printf("gb2312_yes: %d gb2312_no:%d\n", gb2312_yes, gb2312_no); + if ((gb2312_yes + gb2312_no) > 0) { + int ret = (100 * gb2312_yes) / (gb2312_yes + gb2312_no); + if (ret == 100) { + // if (ret > 90) { + // gb2312SCByteChars = gb2312_yes; + return gb2312_yes; + } else { + return 0; + } + } + return 0; +} + +int StringUtils::is_big5_code(char* str, int length) { + unsigned char one_byte = 0X00; // binary 00000000 + + int big5_yes = 0; + int big5_no = 0; + + unsigned char k = 0; + + unsigned char c = 0; + for (int i = 0; i < length;) { + c = (unsigned char)str[i]; + if (c >> 7 == one_byte) { + i++; + continue; + } else if (c >= 0XA1 && c <= 0XF9) { + k = (unsigned char)str[i + 1]; + if ((k >= 0X40 && k <= 0X7E) || (k >= 0XA1 && k <= 0XFE)) { + big5_yes++; + i += 2; + continue; + } + } + + big5_no++; + i += 2; + } + + // printf("%d %d\n", big5_yes, big5_no); + if ((big5_yes + big5_no) > 0) { + int ret = (100 * big5_yes) / (big5_yes + big5_no); + if (ret == 100) { + // if (ret > 90) { + // big5TWBytesChars = big5_yes; + return big5_yes; + } else { + return 0; + } + } + return 0; +} + +int StringUtils::is_gbk_code(char* str, int length) { + unsigned char one_byte = 0X00; // binary 00000000 + + int gbk_yes = 0; + int gbk_no = 0; + + unsigned char k = 0; + + unsigned char c = 0; + for (int i = 0; i < length;) { + c = (unsigned char)str[i]; + if (c >> 7 == one_byte) { + i++; + continue; + } else if (c >= 0X81 && c <= 0XFE) { + k = (unsigned char)str[i + 1]; + if (k >= 0X40 && k <= 0XFE) { + gbk_yes++; + i += 2; + continue; + } + } + + gbk_no++; + i += 2; + } + + // printf("gbk_yes: %d gbk_no:%d\n", gbk_yes, gbk_no); + if ((gbk_yes + gbk_no) > 0) { + int ret = (100 * gbk_yes) / (gbk_yes + gbk_no); + if (ret == 100) { + // if (ret > 90) { + return 1; + } else { + return 0; + } + } + return 0; +} + +int StringUtils::is_ascii_code(char* str, int length) { + unsigned char c = 0; + + bool isASCII = true; + + for (int i = 0; i < length; i++) { + c = (unsigned char)str[i]; + + if ((c > 127)) { + isASCII = false; + } + } + return (isASCII ? 1 : -1); +} + +//#define DEBUG + +int StringUtils::shift_jis_to_jis(const unsigned char* may_be_shift_jis, int* jis_first_ptr, + int* jis_second_ptr) { + int status = 0; + unsigned char first = may_be_shift_jis[0]; + unsigned char second = may_be_shift_jis[1]; + int jis_first = 0; + int jis_second = 0; + /* Check first byte is valid shift JIS. */ + if ((first >= 0x81 && first <= 0x84) || (first >= 0x87 && first <= 0x9f)) { + jis_first = 2 * (first - 0x70) - 1; + if (second >= 0x40 && second <= 0x9e) { + jis_second = second - 31; + if (jis_second > 95) { + jis_second -= 1; + } + + status = 1; + } else if (second >= 0x9f && second <= 0xfc) { + jis_second = second - 126; + jis_first += 1; + status = 1; + } else { + } + } else if (first >= 0xe0 && first <= 0xef) { + jis_first = 2 * (first - 0xb0) - 1; + if (second >= 0x40 && second <= 0x9e) { + jis_second = second - 31; + if (jis_second > 95) { + jis_second -= 1; + } + status = 1; + } else if (second >= 0x9f && second <= 0xfc) { + jis_second = second - 126; + jis_first += 1; + status = 1; + } + } else { + } + *jis_first_ptr = jis_first; + *jis_second_ptr = jis_second; + return status; +} diff --git a/modules/wechat_qrcode/src/zxing/common/stringutils.hpp b/modules/wechat_qrcode/src/zxing/common/stringutils.hpp new file mode 100644 index 0000000..cf58cbd --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/stringutils.hpp @@ -0,0 +1,65 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_COMMON_STRINGUTILS_HPP__ +#define __ZXING_COMMON_STRINGUTILS_HPP__ + +#include "../decodehints.hpp" +#include "../zxing.hpp" + +#include +#include + +namespace zxing { +namespace common { +class StringUtils; +} +} // namespace zxing +using namespace std; + +class zxing::common::StringUtils { +private: + static char const* const PLATFORM_DEFAULT_ENCODING; + +public: + static char const* const ASCII; + static char const* const SHIFT_JIS; + static char const* const GB2312; + static char const* const EUC_JP; + static char const* const UTF8; + static char const* const ISO88591; + static char const* const GBK; + static char const* const GB18030; + static char const* const BIG5; + + static const bool ASSUME_SHIFT_JIS; + + static std::string guessEncoding(char* bytes, int length); + static std::string guessEncodingZXing(char* bytes, int length); + +#ifdef USE_UCHARDET + static std::string guessEncodingUCharDet(char* bytes, int length); +#endif + + static int is_utf8_special_byte(unsigned char c); + // static int is_utf8_code(const string& str); + static int is_utf8_code(char* str, int length); + static int is_gb2312_code(char* str, int length); + static int is_big5_code(char* str, int length); + static int is_gbk_code(char* str, int length); + static int is_ascii_code(char* str, int length); + static int shift_jis_to_jis(const unsigned char* may_be_shift_jis, int* jis_first_ptr, + int* jis_second_ptr); + + static std::string convertString(const char* rawData, int length, const char* fromCharset, + const char* toCharset); +}; + +#endif // __ZXING_COMMON_STRINGUTILS_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/common/unicomblock.cpp b/modules/wechat_qrcode/src/zxing/common/unicomblock.cpp new file mode 100644 index 0000000..5c10a2d --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/unicomblock.cpp @@ -0,0 +1,127 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "../../precomp.hpp" +#include "unicomblock.hpp" + +namespace zxing { +short UnicomBlock::SEARCH_POS[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; +UnicomBlock::UnicomBlock(int iMaxHeight, int iMaxWidth) + : m_iHeight(iMaxHeight), m_iWidth(iMaxWidth), m_bInit(false) {} + +UnicomBlock::~UnicomBlock() {} + +void UnicomBlock::Init() { + if (m_bInit) return; + m_vcIndex = std::vector(m_iHeight * m_iWidth, 0); + m_vcCount = std::vector(m_iHeight * m_iWidth, 0); + m_vcMinPnt = std::vector(m_iHeight * m_iWidth, 0); + m_vcMaxPnt = std::vector(m_iHeight * m_iWidth, 0); + m_vcQueue = std::vector(m_iHeight * m_iWidth, 0); + m_bInit = true; +} + +void UnicomBlock::Reset(Ref poImage) { + m_poImage = poImage; + memset(&m_vcIndex[0], 0, m_vcIndex.size() * sizeof(short)); + m_iNowIdx = 0; +} + +unsigned short UnicomBlock::GetUnicomBlockIndex(int y, int x) { + if (y >= m_iHeight || x >= m_iWidth) return 0; + if (m_vcIndex[y * m_iWidth + x]) return m_vcIndex[y * m_iWidth + x]; + Bfs(y, x); + return m_vcIndex[y * m_iWidth + x]; +} + +int UnicomBlock::GetUnicomBlockSize(int y, int x) { + if (y >= m_iHeight || x >= m_iWidth) return 0; + if (m_vcIndex[y * m_iWidth + x]) return m_vcCount[y * m_iWidth + x]; + Bfs(y, x); + return m_vcCount[y * m_iWidth + x]; +} + +int UnicomBlock::GetMinPoint(int y, int x, int &iMinY, int &iMinX) { + if (y >= m_iHeight || x >= m_iWidth) return -1; + if (m_vcIndex[y * m_iWidth + x]) { + iMinY = m_vcMinPnt[y * m_iWidth + x] >> 16; + iMinX = m_vcMinPnt[y * m_iWidth + x] & (0xFFFF); + return 0; + } + Bfs(y, x); + iMinY = m_vcMinPnt[y * m_iWidth + x] >> 16; + iMinX = m_vcMinPnt[y * m_iWidth + x] & (0xFFFF); + return 0; +} + +int UnicomBlock::GetMaxPoint(int y, int x, int &iMaxY, int &iMaxX) { + if (y >= m_iHeight || x >= m_iWidth) return -1; + if (m_vcIndex[y * m_iWidth + x]) { + iMaxY = m_vcMaxPnt[y * m_iWidth + x] >> 16; + iMaxX = m_vcMaxPnt[y * m_iWidth + x] & (0xFFFF); + return 0; + } + Bfs(y, x); + iMaxY = m_vcMaxPnt[y * m_iWidth + x] >> 16; + iMaxX = m_vcMaxPnt[y * m_iWidth + x] & (0xFFFF); + return 0; +} + +void UnicomBlock::Bfs(int y, int x) { + m_iNowIdx++; + + int iFront = 0; + int iTail = 0; + int iCount = 1; + + int iMaxX = x, iMaxY = y; + int iMinX = x, iMinY = y; + + const bool bValue = (m_poImage->get(x, y) != (unsigned char)0); + + m_vcIndex[y * m_iWidth + x] = m_iNowIdx; + m_vcQueue[iTail++] = y << 16 | x; + + while (iFront < iTail) { + int iNode = m_vcQueue[iFront++]; + int iX = iNode & (0xFFFF); + int iY = iNode >> 16; + iMaxX = max(iX, iMaxX); + iMaxY = max(iY, iMaxY); + iMinX = min(iX, iMinX); + iMinY = min(iY, iMinY); + + iCount++; + + for (int i = 0; i < 4; ++i) { + const int iNextX = iX + SEARCH_POS[i][0], iNextY = iY + SEARCH_POS[i][1]; + const int iPosition = iNextY * m_iWidth + iNextX; + + if (iPosition >= 0 && iPosition < int(m_vcIndex.size()) && 0 == m_vcIndex[iPosition]) { + if (iNextX < 0 || iNextX >= m_poImage->getWidth() || iNextY < 0 || + iNextY >= m_poImage->getHeight() || + bValue != (m_poImage->get(iNextX, iNextY) != (unsigned char)0)) + continue; + + m_vcIndex[iPosition] = m_iNowIdx; + m_vcQueue[iTail++] = iNextY << 16 | iNextX; + } + } + } + + if (iCount >= (1 << 16) - 1) iCount = 0xFFFF; + + const int iMinCombine = iMinY << 16 | iMinX; + const int iMaxCombine = iMaxY << 16 | iMaxX; + for (int i = 0; i < iTail; ++i) { + const int iPosition = (m_vcQueue[i] >> 16) * m_iWidth + (m_vcQueue[i] & (0xFFFF)); + + m_vcCount[iPosition] = iCount; + m_vcMinPnt[iPosition] = iMinCombine; + m_vcMaxPnt[iPosition] = iMaxCombine; + } +} +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/common/unicomblock.hpp b/modules/wechat_qrcode/src/zxing/common/unicomblock.hpp new file mode 100644 index 0000000..9b6871b --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/common/unicomblock.hpp @@ -0,0 +1,47 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __ZXING_COMMON_UNICOMBLOCK_HPP__ +#define __ZXING_COMMON_UNICOMBLOCK_HPP__ +#include "bitmatrix.hpp" +#include "counted.hpp" + +namespace zxing { +class UnicomBlock : public Counted { +public: + UnicomBlock(int iMaxHeight, int iMaxWidth); + ~UnicomBlock(); + + void Init(); + void Reset(Ref poImage); + + unsigned short GetUnicomBlockIndex(int y, int x); + + int GetUnicomBlockSize(int y, int x); + + int GetMinPoint(int y, int x, int &iMinY, int &iMinX); + int GetMaxPoint(int y, int x, int &iMaxY, int &iMaxX); + +private: + void Bfs(int y, int x); + + int m_iHeight; + int m_iWidth; + + unsigned short m_iNowIdx; + bool m_bInit; + std::vector m_vcIndex; + std::vector m_vcCount; + std::vector m_vcMinPnt; + std::vector m_vcMaxPnt; + std::vector m_vcQueue; + static short SEARCH_POS[4][2]; + + Ref m_poImage; +}; +} // namespace zxing +#endif // __ZXING_COMMON_UNICOMBLOCK_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/decodehints.hpp b/modules/wechat_qrcode/src/zxing/decodehints.hpp new file mode 100644 index 0000000..ed1830b --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/decodehints.hpp @@ -0,0 +1,30 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_DECODEHINTS_HPP__ +#define __ZXING_DECODEHINTS_HPP__ + +#include "errorhandler.hpp" + +namespace zxing { +class DecodeHints { +private: + bool use_nn_detector_; + +public: + explicit DecodeHints(bool use_nn_detector = false) : use_nn_detector_(use_nn_detector){}; + + bool getUseNNDetector() const { return use_nn_detector_; } + void setUseNNDetector(bool use_nn_detector) { use_nn_detector_ = use_nn_detector; } +}; + +} // namespace zxing + +#endif // __ZXING_DECODEHINTS_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/errorhandler.cpp b/modules/wechat_qrcode/src/zxing/errorhandler.cpp new file mode 100644 index 0000000..c8604ab --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/errorhandler.cpp @@ -0,0 +1,49 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +#include "../precomp.hpp" +#include "errorhandler.hpp" + +namespace zxing { + +ErrorHandler::ErrorHandler() : err_code_(0), err_msg_("") { Init(); } + +ErrorHandler::ErrorHandler(const char* err_msg) : err_code_(-1), err_msg_(err_msg) { Init(); } + +ErrorHandler::ErrorHandler(std::string& err_msg) : err_code_(-1), err_msg_(err_msg) { Init(); } + +ErrorHandler::ErrorHandler(int err_code) : err_code_(err_code), err_msg_("error") { Init(); } + +ErrorHandler::ErrorHandler(int err_code, const char* err_msg) + : err_code_(err_code), err_msg_(err_msg) { + Init(); +} + +ErrorHandler::ErrorHandler(const ErrorHandler& other) { + err_code_ = other.ErrCode(); + err_msg_.assign(other.ErrMsg()); + Init(); +} + +ErrorHandler& ErrorHandler::operator=(const ErrorHandler& other) { + err_code_ = other.ErrCode(); + err_msg_.assign(other.ErrMsg()); + Init(); + return *this; +} + +void ErrorHandler::Init() { handler_type_ = KErrorHandler; } + +void ErrorHandler::Reset() { + err_code_ = 0; + err_msg_.assign(""); +} + +void ErrorHandler::PrintInfo() { + printf("handler_tpye %d, error code %d, errmsg %s\n", handler_type_, err_code_, + err_msg_.c_str()); +} +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/errorhandler.hpp b/modules/wechat_qrcode/src/zxing/errorhandler.hpp new file mode 100644 index 0000000..486786b --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/errorhandler.hpp @@ -0,0 +1,89 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __ZXING_ERRORHANDLER_HPP__ +#define __ZXING_ERRORHANDLER_HPP__ + +#include + +namespace zxing { + +enum { + KErrorHandler = 0, + KErrorHandler_NotFound = 1, + KErrorHandler_CheckSum = 2, + KErrorHandler_Reader = 3, + KErrorHandler_IllegalArgument = 4, + KErrorHandler_ReedSolomon = 5, + KErrorHandler_Format = 6, + KErrorHandler_Detector = 7, + KErrorHandler_IllegalState = 8, +}; + +class ErrorHandler { +public: + ErrorHandler(); + explicit ErrorHandler(std::string& err_msg); + explicit ErrorHandler(const char* err_msg); + explicit ErrorHandler(int err_code); + ErrorHandler(int err_code, std::string& err_msg); + ErrorHandler(int err_code, const char* err_msg); + + virtual ~ErrorHandler(){}; + + virtual inline int ErrCode() const { return err_code_; } + virtual inline const std::string& ErrMsg() const { return err_msg_; } + virtual inline int HandlerType() const { return handler_type_; } + + virtual void Init(); + ErrorHandler(const ErrorHandler& other); + ErrorHandler& operator=(const ErrorHandler& other); + + virtual void PrintInfo(); + virtual void Reset(); + +protected: + int handler_type_; + +private: + int err_code_; + std::string err_msg_; +}; + +#define DECLARE_ERROR_HANDLER(__HANDLER__) \ + class __HANDLER__##ErrorHandler : public ErrorHandler { \ + public: \ + __HANDLER__##ErrorHandler() : ErrorHandler() { Init(); }; \ + __HANDLER__##ErrorHandler(std::string& err_msg) : ErrorHandler(err_msg) { Init(); }; \ + __HANDLER__##ErrorHandler(const char* err_msg) : ErrorHandler(err_msg) { Init(); }; \ + __HANDLER__##ErrorHandler(int err_code) : ErrorHandler(err_code) { Init(); }; \ + __HANDLER__##ErrorHandler(int err_code, std::string& err_msg) \ + : ErrorHandler(err_code, err_msg) { \ + Init(); \ + }; \ + __HANDLER__##ErrorHandler(int err_code, const char* err_msg) \ + : ErrorHandler(err_code, err_msg) { \ + Init(); \ + }; \ + __HANDLER__##ErrorHandler(const ErrorHandler& other) : ErrorHandler(other) { Init(); }; \ + void Init() override { handler_type_ = KErrorHandler_##__HANDLER__; } \ + }; + +DECLARE_ERROR_HANDLER(Reader) +DECLARE_ERROR_HANDLER(IllegalArgument) +DECLARE_ERROR_HANDLER(ReedSolomon) +DECLARE_ERROR_HANDLER(Format) +DECLARE_ERROR_HANDLER(Detector) +DECLARE_ERROR_HANDLER(NotFound) +DECLARE_ERROR_HANDLER(CheckSum) +DECLARE_ERROR_HANDLER(IllegalState) + +#undef DECLARE_ERROR_HANDLER + +} // namespace zxing + +#endif // __ZXING_ERRORHANDLER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/luminance_source.cpp b/modules/wechat_qrcode/src/zxing/luminance_source.cpp new file mode 100644 index 0000000..bb0b41b --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/luminance_source.cpp @@ -0,0 +1,59 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../precomp.hpp" +#include "luminance_source.hpp" +#include + +using zxing::LuminanceSource; +using zxing::Ref; + +LuminanceSource::LuminanceSource(int width, int height) + : width_(width), height_(height) {} + +LuminanceSource::~LuminanceSource() {} + +bool LuminanceSource::isCropSupported() const { return false; } + +Ref LuminanceSource::crop(int, int, int, int, zxing::ErrorHandler&) const { + return Ref(); +} + +bool LuminanceSource::isRotateSupported() const { return false; } + +Ref LuminanceSource::rotateCounterClockwise(zxing::ErrorHandler&) const { + return Ref(); +} + +LuminanceSource::operator std::string() const { + ArrayRef row; + std::ostringstream oss; + zxing::ErrorHandler err_handler; + for (int y = 0; y < getHeight(); y++) { + err_handler.Reset(); + row = getRow(y, row, err_handler); + if (err_handler.ErrCode()) continue; + for (int x = 0; x < getWidth(); x++) { + int luminance = row[x] & 0xFF; + char c; + if (luminance < 0x40) { + c = '#'; + } else if (luminance < 0x80) { + c = '+'; + } else if (luminance < 0xC0) { + c = '.'; + } else { + c = ' '; + } + oss << c; + } + oss << '\n'; + } + return oss.str(); +} diff --git a/modules/wechat_qrcode/src/zxing/luminance_source.hpp b/modules/wechat_qrcode/src/zxing/luminance_source.hpp new file mode 100644 index 0000000..e343572 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/luminance_source.hpp @@ -0,0 +1,57 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_LUMINANCE_SOURCE_HPP__ +#define __ZXING_LUMINANCE_SOURCE_HPP__ + +#include +#include "common/array.hpp" +#include "common/bytematrix.hpp" +#include "common/counted.hpp" +#include "errorhandler.hpp" + +namespace zxing { + +class LuminanceSource : public Counted { +protected: + int width_; + int height_; + +public: + LuminanceSource(int width, int height); + virtual ~LuminanceSource(); + + int getWidth() const { return width_; } + int getHeight() const { return height_; } + void setWidth(int w) { width_ = w; } + void setHeight(int h) { height_ = h; } + void filter(); + + // Callers take ownership of the returned memory and must call delete [] on + // it themselves. + virtual ArrayRef getRow(int y, ArrayRef row, + zxing::ErrorHandler& err_handler) const = 0; + virtual ArrayRef getMatrix() const = 0; + virtual Ref getByteMatrix() const = 0; + + virtual bool isCropSupported() const; + virtual Ref crop(int left, int top, int width, int height, + zxing::ErrorHandler& err_handler) const; + + virtual bool isRotateSupported() const; + + virtual Ref rotateCounterClockwise(zxing::ErrorHandler& err_handler) const; + + operator std::string() const; +}; + +} // namespace zxing + +#endif // __ZXING_LUMINANCE_SOURCE_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/bitmatrixparser.cpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/bitmatrixparser.cpp new file mode 100644 index 0000000..6d426d0 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/bitmatrixparser.cpp @@ -0,0 +1,240 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "bitmatrixparser.hpp" +#include "datamask.hpp" + +using zxing::ErrorHandler; + +namespace zxing { +namespace qrcode { + +int BitMatrixParser::copyBit(size_t x, size_t y, int versionBits) { + bool bit = ((mirror_ ? bitMatrix_->get(y, x) : bitMatrix_->get(x, y)) != (unsigned char)0); + return bit ? (versionBits << 1) | 0x1 : versionBits << 1; +} + +BitMatrixParser::BitMatrixParser(Ref bitMatrix, ErrorHandler &err_handler) + : bitMatrix_(bitMatrix), parsedVersion_(0), parsedFormatInfo_() { + mirror_ = false; + size_t dimension = bitMatrix->getHeight(); + + if ((dimension < 21) || (dimension & 0x03) != 1) { + err_handler = zxing::ReaderErrorHandler("Dimension must be 1 mod 4 and >= 21"); + return; + } +} + +Ref BitMatrixParser::readFormatInformation(ErrorHandler &err_handler) { + if (parsedFormatInfo_ != 0) { + 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 + 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_ != 0) { + return parsedFormatInfo_; + } + err_handler = zxing::ReaderErrorHandler("Could not decode format information"); + return Ref(); +} + +Version *BitMatrixParser::readVersion(ErrorHandler &err_handler) { + if (parsedVersion_ != 0) { + return parsedVersion_; + } + + int dimension = bitMatrix_->getHeight(); + + int provisionalVersion = (dimension - 17) >> 2; + if (provisionalVersion <= 6) { + Version *version = Version::getVersionForNumber(provisionalVersion, err_handler); + if (err_handler.ErrCode()) return NULL; + return version; + } + + // Read top-right version info: 3 wide by 6 tall + int versionBits = 0; + for (int y = 5; y >= 0; y--) { + int xMin = dimension - 11; + for (int x = dimension - 9; x >= xMin; x--) { + versionBits = copyBit(x, y, versionBits); + } + } + + parsedVersion_ = Version::decodeVersionInformation(versionBits); + if (parsedVersion_ != 0 && parsedVersion_->getDimensionForVersion(err_handler) == dimension) { + return parsedVersion_; + } + + // Hmm, failed. Try bottom left: 6 wide by 3 tall + versionBits = 0; + for (int x = 5; x >= 0; x--) { + int yMin = dimension - 11; + for (int y = dimension - 9; y >= yMin; y--) { + versionBits = copyBit(x, y, versionBits); + } + } + + parsedVersion_ = Version::decodeVersionInformation(versionBits); + if (parsedVersion_ == NULL) { + err_handler = zxing::ReaderErrorHandler("Could not decode version"); + return NULL; + } + + if (parsedVersion_ != 0 && parsedVersion_->getDimensionForVersion(err_handler) == dimension) { + return parsedVersion_; + } + + err_handler = zxing::ReaderErrorHandler("Could not decode version"); + return NULL; +} + +/** + *

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 + */ +ArrayRef BitMatrixParser::readCodewords(ErrorHandler &err_handler) { + Ref formatInfo = readFormatInformation(err_handler); + if (err_handler.ErrCode()) return ArrayRef(); + + Version *version = readVersion(err_handler); + if (err_handler.ErrCode()) return ArrayRef(); + + DataMask &dataMask = DataMask::forReference((int)formatInfo->getDataMask(), err_handler); + if (err_handler.ErrCode()) return ArrayRef(); + // cout << (int)formatInfo->getDataMask() << endl; + int dimension = bitMatrix_->getHeight(); + + dataMask.unmaskBitMatrix(*bitMatrix_, dimension); + + // cerr << *bitMatrix_ << endl; + // cerr << version->getTotalCodewords() << endl; + + Ref functionPattern = version->buildFunctionPattern(err_handler); + if (err_handler.ErrCode()) return ArrayRef(); + + // cout << *functionPattern << endl; + + bool readingUp = true; + ArrayRef result(version->getTotalCodewords()); + int resultOffset = 0; + int currentByte = 0; + int bitsRead = 0; + // Read columns in pairs, from right to left + for (int x = dimension - 1; x > 0; x -= 2) { + if (x == 6) { + // Skip whole column with vertical alignment pattern; + // saves time and makes the other code proceed more cleanly + x--; + } + // Read alternatingly from bottom to top then top to bottom + for (int counter = 0; counter < dimension; counter++) { + int y = readingUp ? dimension - 1 - counter : counter; + for (int col = 0; col < 2; col++) { + // Ignore bits covered by the function pattern + if (!functionPattern->get(x - col, y)) { + // Read a bit + bitsRead++; + currentByte <<= 1; + if (bitMatrix_->get(x - col, y)) { + currentByte |= 1; + } + // If we've made a whole byte, save it off + if (bitsRead == 8) { + result[resultOffset++] = (char)currentByte; + bitsRead = 0; + currentByte = 0; + } + } + } + } + readingUp = !readingUp; // switch directions + } + + if (resultOffset != version->getTotalCodewords()) { + err_handler = zxing::ReaderErrorHandler("Did not read all codewords"); + return ArrayRef(); + } + + return result; +} + +/** + * Revert the mask removal done while reading the code words. The bit matrix + * should revert to its original state. + */ +void BitMatrixParser::remask() { + if (parsedFormatInfo_ == NULL) { + return; // We have no format information, and have no data mask + } + ErrorHandler err_handler; + DataMask &dataMask = DataMask::forReference(parsedFormatInfo_->getDataMask(), err_handler); + if (err_handler.ErrCode()) return; + 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 BitMatrixParser::setMirror(bool mirror) { + parsedVersion_ = NULL; + parsedFormatInfo_ = NULL; + mirror_ = mirror; +} + +/** Mirror the bit matrix in order to attempt a second reading. */ +void BitMatrixParser::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); + } + } + } +} + +} // namespace qrcode +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/bitmatrixparser.hpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/bitmatrixparser.hpp new file mode 100644 index 0000000..ff5beec --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/bitmatrixparser.hpp @@ -0,0 +1,53 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DECODER_BITMATRIXPARSER_HPP__ +#define __ZXING_QRCODE_DECODER_BITMATRIXPARSER_HPP__ + +#include "../../common/array.hpp" +#include "../../common/bitmatrix.hpp" +#include "../../common/counted.hpp" +#include "../../errorhandler.hpp" +#include "../format_information.hpp" +#include "../version.hpp" + +namespace zxing { +namespace qrcode { + +class BitMatrixParser : public Counted { +private: + Ref bitMatrix_; + Version *parsedVersion_; + Ref parsedFormatInfo_; + bool mirror_; + + int copyBit(size_t x, size_t y, int versionBits); + +public: + BitMatrixParser(Ref bitMatrix, ErrorHandler &err_handler); + Ref readFormatInformation(ErrorHandler &err_handler); + Version *readVersion(ErrorHandler &err_handler); + ArrayRef readCodewords(ErrorHandler &err_handler); + +public: + void remask(); + void setMirror(bool mirror); + void mirror(); + void mirrorH(); + +private: + BitMatrixParser(const BitMatrixParser &); + BitMatrixParser &operator=(const BitMatrixParser &); +}; + +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DECODER_BITMATRIXPARSER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/datablock.cpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/datablock.cpp new file mode 100644 index 0000000..da6afaa --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/datablock.cpp @@ -0,0 +1,103 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "datablock.hpp" +namespace zxing { +namespace qrcode { + +using zxing::ErrorHandler; + +DataBlock::DataBlock(int numDataCodewords, ArrayRef codewords) + : numDataCodewords_(numDataCodewords), codewords_(codewords) {} + +int DataBlock::getNumDataCodewords() { return numDataCodewords_; } + +ArrayRef DataBlock::getCodewords() { return codewords_; } + +std::vector > DataBlock::getDataBlocks(ArrayRef rawCodewords, Version *version, + ErrorCorrectionLevel &ecLevel, + ErrorHandler &err_handler) { + // Figure out the number and size of data blocks used by this version and + // error correction level + ECBlocks &ecBlocks = version->getECBlocksForLevel(ecLevel); + + // First count the total number of data blocks + int totalBlocks = 0; + vector ecBlockArray = ecBlocks.getECBlocks(); + for (size_t i = 0; i < ecBlockArray.size(); i++) { + totalBlocks += ecBlockArray[i]->getCount(); + } + + // Now establish DataBlocks of the appropriate size and number of data + // codewords + std::vector > result(totalBlocks); + int numResultBlocks = 0; + for (size_t j = 0; j < ecBlockArray.size(); j++) { + ECB *ecBlock = ecBlockArray[j]; + for (int i = 0; i < ecBlock->getCount(); i++) { + int numDataCodewords = ecBlock->getDataCodewords(); + int numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords; + ArrayRef buffer(numBlockCodewords); + Ref blockRef(new DataBlock(numDataCodewords, buffer)); + result[numResultBlocks++] = blockRef; + } + } + + // 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_->size(); + int longerBlocksStartAt = result.size() - 1; + while (longerBlocksStartAt >= 0) { + int numCodewords = result[longerBlocksStartAt]->codewords_->size(); + if (numCodewords == shorterBlocksTotalCodewords) { + break; + } + if (numCodewords != shorterBlocksTotalCodewords + 1) { + err_handler = + zxing::IllegalArgumentErrorHandler("Data block sizes differ by more than 1"); + return std::vector >(); + } + longerBlocksStartAt--; + } + longerBlocksStartAt++; + + int shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getECCodewords(); + // 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_->size(); + 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++]; + } + } + + if (rawCodewordsOffset != rawCodewords->size()) { + err_handler = + zxing::IllegalArgumentErrorHandler("rawCodewordsOffset != rawCodewords.length"); + return std::vector >(); + } + + return result; +} + +} // namespace qrcode +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/datablock.hpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/datablock.hpp new file mode 100644 index 0000000..f95a29e --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/datablock.hpp @@ -0,0 +1,43 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DECODER_DATABLOCK_HPP__ +#define __ZXING_QRCODE_DECODER_DATABLOCK_HPP__ + +#include "../../common/array.hpp" +#include "../../common/counted.hpp" +#include "../../errorhandler.hpp" +#include "../error_correction_level.hpp" +#include "../version.hpp" + + +namespace zxing { +namespace qrcode { + +class DataBlock : public Counted { +private: + int numDataCodewords_; + ArrayRef codewords_; + + DataBlock(int numDataCodewords, ArrayRef codewords); + +public: + static std::vector > getDataBlocks(ArrayRef rawCodewords, Version *version, + ErrorCorrectionLevel &ecLevel, + ErrorHandler &err_handler); + + int getNumDataCodewords(); + ArrayRef getCodewords(); +}; + +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DECODER_DATABLOCK_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/datamask.cpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/datamask.cpp new file mode 100644 index 0000000..68ccb6f --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/datamask.cpp @@ -0,0 +1,120 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "datamask.hpp" +namespace zxing { +namespace qrcode { + +using zxing::ErrorHandler; + +DataMask::DataMask() {} + +DataMask::~DataMask() {} + +DataMask& DataMask::forReference(int reference, ErrorHandler& err_handler) { + if (reference < 0 || reference > 7) { + err_handler = zxing::IllegalArgumentErrorHandler("reference must be between 0 and 7"); + return *DATA_MASKS[0]; + } + return *DATA_MASKS[reference]; +} + +void DataMask::unmaskBitMatrix(BitMatrix& bits, size_t dimension) { + for (size_t y = 0; y < dimension; y++) { + for (size_t x = 0; x < dimension; x++) { + // TODO: check why the coordinates have to be swapped + if (isMasked(y, x)) { + bits.flip(x, y); + } + } + } +} + +/** + * 000: mask bits for which (x + y) mod 2 == 0 + */ +class DataMask000 : public DataMask { +public: + bool isMasked(size_t x, size_t y) override { return ((x + y) % 2) == 0; } +}; + +/** + * 001: mask bits for which x mod 2 == 0 + */ +class DataMask001 : public DataMask { +public: + bool isMasked(size_t x, size_t) override { return (x % 2) == 0; } +}; + +/** + * 010: mask bits for which y mod 3 == 0 + */ +class DataMask010 : public DataMask { +public: + bool isMasked(size_t, size_t y) override { return y % 3 == 0; } +}; + +/** + * 011: mask bits for which (x + y) mod 3 == 0 + */ +class DataMask011 : public DataMask { +public: + bool isMasked(size_t x, size_t y) override { return (x + y) % 3 == 0; } +}; + +/** + * 100: mask bits for which (x/2 + y/3) mod 2 == 0 + */ +class DataMask100 : public DataMask { +public: + bool isMasked(size_t x, size_t y) override { return (((x >> 1) + (y / 3)) % 2) == 0; } +}; + +/** + * 101: mask bits for which xy mod 2 + xy mod 3 == 0 + */ +class DataMask101 : public DataMask { +public: + bool isMasked(size_t x, size_t y) override { + size_t temp = x * y; + return (temp % 2) + (temp % 3) == 0; + } +}; + +/** + * 110: mask bits for which (xy mod 2 + xy mod 3) mod 2 == 0 + */ +class DataMask110 : public DataMask { +public: + bool isMasked(size_t x, size_t y) override { + size_t temp = x * y; + return (((temp % 2) + (temp % 3)) % 2) == 0; + } +}; + +/** + * 111: mask bits for which ((x+y)mod 2 + xy mod 3) mod 2 == 0 + */ +class DataMask111 : public DataMask { +public: + bool isMasked(size_t x, size_t y) override { + return ((((x + y) % 2) + ((x * y) % 3)) % 2) == 0; + } +}; + +vector > DataMask::DATA_MASKS = { + Ref(new DataMask000()), Ref(new DataMask001()), + Ref(new DataMask010()), Ref(new DataMask011()), + Ref(new DataMask100()), Ref(new DataMask101()), + Ref(new DataMask110()), Ref(new DataMask111()), +}; + +} // namespace qrcode +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/datamask.hpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/datamask.hpp new file mode 100644 index 0000000..c8f276f --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/datamask.hpp @@ -0,0 +1,37 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DECODER_DATAMASK_HPP__ +#define __ZXING_QRCODE_DECODER_DATAMASK_HPP__ + +#include "../../common/array.hpp" +#include "../../common/bitmatrix.hpp" +#include "../../common/counted.hpp" +#include "../../errorhandler.hpp" +namespace zxing { +namespace qrcode { + +class DataMask : public Counted { +private: + static std::vector > DATA_MASKS; + +protected: +public: + DataMask(); + virtual ~DataMask(); + void unmaskBitMatrix(BitMatrix& matrix, size_t dimension); + virtual bool isMasked(size_t x, size_t y) = 0; + static DataMask& forReference(int reference, ErrorHandler& err_handler); +}; + +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DECODER_DATAMASK_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoded_bit_stream_parser.cpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoded_bit_stream_parser.cpp new file mode 100644 index 0000000..05de793 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoded_bit_stream_parser.cpp @@ -0,0 +1,490 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "decoded_bit_stream_parser.hpp" +#include "../../common/stringutils.hpp" +#include "../../zxing.hpp" +#ifndef NO_ICONV_INSIDE +#include +#endif +#include + +#undef ICONV_CONST +#define ICONV_CONST const + +#ifndef ICONV_CONST +#define ICONV_CONST /**/ +#endif + +using zxing::ErrorHandler; + +// Add this to fix both Mac and Windows compilers +template +class sloppy {}; + +// convert between T** and const T** +template +class sloppy { + T** t; + +public: + explicit sloppy(T** mt) : t(mt) {} + explicit sloppy(const T** mt) : t(const_cast(mt)) {} + + operator T* *() const { return t; } + operator const T* *() const { return const_cast(t); } +}; + +using namespace std; +using namespace zxing; +using namespace zxing::qrcode; +using namespace zxing::common; + +const char DecodedBitStreamParser::ALPHANUMERIC_CHARS[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', + 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', '+', '-', '.', '/', ':'}; + +// string DecodedBitStreamParser::outputCharset = "UTF-8"; + +namespace { +int GB2312_SUBSET = 1; +} + +void DecodedBitStreamParser::append(std::string& result, string const& in, + ErrorHandler& err_handler) { + append(result, (char const*)in.c_str(), in.length(), err_handler); +} + +void DecodedBitStreamParser::append(std::string& result, const char* bufIn, size_t nIn, + ErrorHandler& err_handler) { + if (err_handler.ErrCode()) return; +#ifndef NO_ICONV_INSIDE + if (nIn == 0) { + return; + } + iconv_t cd; + // cout< 0) { + size_t oneway = iconv(cd, sloppy(&fromPtr), &nFrom, sloppy(&toPtr), &nTo); + + if (oneway == (size_t)(-1)) { + iconv_close(cd); + delete[] bufOut; + err_handler = zxing::ReaderErrorHandler("error converting characters"); + return; + } + } + iconv_close(cd); + + int nResult = maxOut - nTo; + bufOut[nResult] = '\0'; + result.append((const char*)bufOut); + delete[] bufOut; +#else + result.append((const char*)bufIn, nIn); +#endif +} + +void DecodedBitStreamParser::decodeHanziSegment(Ref bits_, string& result, int count, + ErrorHandler& err_handler) { + BitSource& bits(*bits_); + // Don't crash trying to read more bits than we have available. + if (count * 13 > bits.available()) { + err_handler = zxing::FormatErrorHandler("decodeKanjiSegment"); + return; + } + + // Each character will require 2 bytes. Read the characters as 2-byte pairs + // and decode as GB2312 afterwards + size_t nBytes = 2 * count; + char* buffer = new char[nBytes]; + int offset = 0; + while (count > 0) { + // Each 13 bits encodes a 2-byte character + int twoBytes = bits.readBits(13, err_handler); + if (err_handler.ErrCode()) return; + int assembledTwoBytes = ((twoBytes / 0x060) << 8) | (twoBytes % 0x060); + if (assembledTwoBytes < 0x003BF) { + // In the 0xA1A1 to 0xAAFE range + assembledTwoBytes += 0x0A1A1; + } else { + // In the 0xB0A1 to 0xFAFE range + assembledTwoBytes += 0x0A6A1; + } + buffer[offset] = (char)((assembledTwoBytes >> 8) & 0xFF); + buffer[offset + 1] = (char)(assembledTwoBytes & 0xFF); + offset += 2; + count--; + } + // for(int i=0;i bits, std::string& result, int count, + ErrorHandler& err_handler) { + // Each character will require 2 bytes. Read the characters as 2-byte pairs + // and decode as Shift_JIS afterwards + size_t nBytes = 2 * count; + char* buffer = new char[nBytes]; + int offset = 0; + while (count > 0) { + // Each 13 bits encodes a 2-byte character + + int twoBytes = bits->readBits(13, err_handler); + if (err_handler.ErrCode()) return; + 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] = (char)(assembledTwoBytes >> 8); + buffer[offset + 1] = (char)assembledTwoBytes; + offset += 2; + count--; + } + + append(result, buffer, nBytes, err_handler); + if (err_handler.ErrCode()) { + delete[] buffer; + return; + } + // cout< bits_, string& result, int count, + CharacterSetECI* currentCharacterSetECI, + ArrayRef >& byteSegments, + ErrorHandler& err_handler) { + int nBytes = count; + BitSource& bits(*bits_); + // Don't crash trying to read more bits than we have available. + int available = bits.available(); + // try to repair count data if count data is invalid + if (count * 8 > available) { + count = (available + 7 / 8); + } + + ArrayRef bytes_(count); + char* readBytes = &(*bytes_)[0]; + for (int i = 0; i < count; i++) { + // readBytes[i] = (char) bits.readBits(8); + int readBits = available < 8 ? available : 8; + readBytes[i] = (char)bits.readBits(readBits, err_handler); + } + if (err_handler.ErrCode()) return; + // vector encoding; + string encoding; + + if (currentCharacterSetECI == 0) { + // 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 = outputCharset; + + } else { + // encoding .push_back(currentCharacterSetECI->name()); + encoding = currentCharacterSetECI->name(); + } + // cout<<"encoding: "<values().push_back(bytes_); +} + +void DecodedBitStreamParser::decodeNumericSegment(Ref bits, std::string& result, + int count, ErrorHandler& err_handler) { + int nBytes = count; + // char* bytes = new char[nBytes]; + ArrayRef bytes = ArrayRef(new Array(nBytes)); + int i = 0; + // Read three digits at a time + while (count >= 3) { + // Each 10 bits encodes three digits + if (bits->available() < 10) { + err_handler = zxing::ReaderErrorHandler("format exception"); + return; + } + int threeDigitsBits = bits->readBits(10, err_handler); + if (err_handler.ErrCode()) return; + if (threeDigitsBits >= 1000) { + ostringstream s; + s << "Illegal value for 3-digit unit: " << threeDigitsBits; + err_handler = zxing::ReaderErrorHandler(s.str().c_str()); + return; + } + bytes[i++] = ALPHANUMERIC_CHARS[threeDigitsBits / 100]; + bytes[i++] = ALPHANUMERIC_CHARS[(threeDigitsBits / 10) % 10]; + bytes[i++] = ALPHANUMERIC_CHARS[threeDigitsBits % 10]; + count -= 3; + } + if (count == 2) { + if (bits->available() < 7) { + err_handler = zxing::ReaderErrorHandler("format exception"); + return; + } + // Two digits left over to read, encoded in 7 bits + int twoDigitsBits = bits->readBits(7, err_handler); + if (err_handler.ErrCode()) return; + if (twoDigitsBits >= 100) { + ostringstream s; + s << "Illegal value for 2-digit unit: " << twoDigitsBits; + err_handler = zxing::ReaderErrorHandler(s.str().c_str()); + return; + } + bytes[i++] = ALPHANUMERIC_CHARS[twoDigitsBits / 10]; + bytes[i++] = ALPHANUMERIC_CHARS[twoDigitsBits % 10]; + } else if (count == 1) { + if (bits->available() < 4) { + err_handler = zxing::ReaderErrorHandler("format exception"); + return; + } + // One digit left over to read + int digitBits = bits->readBits(4, err_handler); + if (err_handler.ErrCode()) return; + if (digitBits >= 10) { + ostringstream s; + s << "Illegal value for digit unit: " << digitBits; + err_handler = zxing::ReaderErrorHandler(s.str().c_str()); + return; + } + bytes[i++] = ALPHANUMERIC_CHARS[digitBits]; + } + append(result, bytes->data(), nBytes, err_handler); + if (err_handler.ErrCode()) return; +} + +char DecodedBitStreamParser::toAlphaNumericChar(size_t value, ErrorHandler& err_handler) { + if (value >= sizeof(DecodedBitStreamParser::ALPHANUMERIC_CHARS)) { + err_handler = zxing::FormatErrorHandler("toAlphaNumericChar"); + return 0; + } + return ALPHANUMERIC_CHARS[value]; +} + +void DecodedBitStreamParser::decodeAlphanumericSegment(Ref bits_, string& result, + int count, bool fc1InEffect, + ErrorHandler& err_handler) { + BitSource& bits(*bits_); + ostringstream bytes; + // Read two characters at a time + while (count > 1) { + if (bits.available() < 11) { + err_handler = zxing::FormatErrorHandler("decodeAlphanumericSegment"); + return; + } + int nextTwoCharsBits = bits.readBits(11, err_handler); + bytes << toAlphaNumericChar(nextTwoCharsBits / 45, err_handler); + bytes << toAlphaNumericChar(nextTwoCharsBits % 45, err_handler); + if (err_handler.ErrCode()) return; + count -= 2; + } + if (count == 1) { + // special case: one character left + if (bits.available() < 6) { + err_handler = zxing::FormatErrorHandler("decodeAlphanumericSegment"); + return; + } + bytes << toAlphaNumericChar(bits.readBits(6, err_handler), err_handler); + if (err_handler.ErrCode()) return; + } + // See section 6.4.8.1, 6.4.8.2 + string s = bytes.str(); + if (fc1InEffect) { + // We need to massage the result a bit if in an FNC1 mode: + ostringstream r; + for (size_t i = 0; i < s.length(); i++) { + if (s[i] != '%') { + r << s[i]; + } else { + if (i < s.length() - 1 && s[i + 1] == '%') { + // %% is rendered as % + r << s[i++]; + } else { + // In alpha mode, % should be converted to FNC1 separator + // 0x1D + r << (char)0x1D; + } + } + } + s = r.str(); + } + append(result, s, err_handler); + if (err_handler.ErrCode()) return; +} + +namespace { +int parseECIValue(BitSource& bits, ErrorHandler& err_handler) { + int firstByte = bits.readBits(8, err_handler); + if (err_handler.ErrCode()) return 0; + if ((firstByte & 0x80) == 0) { + // just one byte + return firstByte & 0x7F; + } + if ((firstByte & 0xC0) == 0x80) { + // two bytes + int secondByte = bits.readBits(8, err_handler); + if (err_handler.ErrCode()) return 0; + return ((firstByte & 0x3F) << 8) | secondByte; + } + if ((firstByte & 0xE0) == 0xC0) { + // three bytes + int secondThirdBytes = bits.readBits(16, err_handler); + if (err_handler.ErrCode()) return 0; + return ((firstByte & 0x1F) << 16) | secondThirdBytes; + } + err_handler = zxing::FormatErrorHandler("parseECIValue"); + return 0; +} +} // namespace + +Ref DecodedBitStreamParser::decode(ArrayRef bytes, Version* version, + ErrorCorrectionLevel const& ecLevel, + ErrorHandler& err_handler, int iVersion) { + Ref bits_(new BitSource(bytes)); + BitSource& bits(*bits_); + string result; + result.reserve(50); + Mode* mode = 0; + string modeName; + ArrayRef > byteSegments(0); + + CharacterSetECI* currentCharacterSetECI = 0; + bool fc1InEffect = false; + + outputCharset = "UTF-8"; + 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, err_handler), + err_handler); // mode is encoded by 4 bits + if (err_handler.ErrCode()) return Ref(); + } + + if (mode != &Mode::TERMINATOR) { + if ((mode == &Mode::FNC1_FIRST_POSITION) || (mode == &Mode::FNC1_SECOND_POSITION)) { + // We do little with FNC1 except alter the parsed result a bit + // according to the spec + fc1InEffect = true; + } else if (mode == &Mode::STRUCTURED_APPEND) { + if (bits.available() < 16) { + err_handler = zxing::FormatErrorHandler("decode"); + return Ref(); + } + // not really supported; all we do is ignore it + // Read next 8 bits (symbol sequence #) and 8 bits (parity + // data), then continue + bits.readBits(16, err_handler); + if (err_handler.ErrCode()) return Ref(); + } else if (mode == &Mode::ECI) { + // Count doesn't apply to ECI + int value = parseECIValue(bits, err_handler); + if (err_handler.ErrCode()) Ref(); + currentCharacterSetECI = CharacterSetECI::getCharacterSetECIByValueFind(value); + if (currentCharacterSetECI == 0) { + err_handler = zxing::FormatErrorHandler("decode"); + return Ref(); + } + } else { + // First handle Hanzi mode which does not start with character + // count + if (mode == &Mode::HANZI) { + // chinese mode contains a sub set indicator right after + // mode indicator + int subset = bits.readBits(4, err_handler); + int countHanzi = + bits.readBits(mode->getCharacterCountBits(version), err_handler); + if (err_handler.ErrCode()) return Ref(); + if (subset == GB2312_SUBSET) { + decodeHanziSegment(bits_, result, countHanzi, err_handler); + if (err_handler.ErrCode()) Ref(); + outputCharset = "GB2312"; + modeName = mode->getName(); + } + } else { + // "Normal" QR code modes: + // How many characters will follow, encoded in this mode? + int count = bits.readBits(mode->getCharacterCountBits(version), err_handler); + if (err_handler.ErrCode()) return Ref(); + if (mode == &Mode::NUMERIC) { + decodeNumericSegment(bits_, result, count, err_handler); + if (err_handler.ErrCode()) { + err_handler = zxing::FormatErrorHandler("decode"); + return Ref(); + } + modeName = mode->getName(); + } else if (mode == &Mode::ALPHANUMERIC) { + decodeAlphanumericSegment(bits_, result, count, fc1InEffect, err_handler); + if (err_handler.ErrCode()) Ref(); + modeName = mode->getName(); + } else if (mode == &Mode::BYTE) { + decodeByteSegment(bits_, result, count, currentCharacterSetECI, + byteSegments, err_handler); + if (err_handler.ErrCode()) { + err_handler = zxing::FormatErrorHandler("decode"); + return Ref(); + } + modeName = mode->getName(); + // outputCharset = getResultCharset(); + } else if (mode == &Mode::KANJI) { + // int countKanji = + // bits.readBits(mode->getCharacterCountBits(version)); + // cout<<"countKanji: "<(); + modeName = mode->getName(); + } else { + err_handler = zxing::FormatErrorHandler("decode"); + return Ref(); + } + } + } + } + } while (mode != &Mode::TERMINATOR); + return Ref(new DecoderResult(bytes, Ref(new String(result)), + byteSegments, (string)ecLevel, + (string)outputCharset, iVersion, modeName)); +} diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoded_bit_stream_parser.hpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoded_bit_stream_parser.hpp new file mode 100644 index 0000000..e8887ef --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoded_bit_stream_parser.hpp @@ -0,0 +1,66 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DECODER_DECODEDBITSTREAMPARSER_HPP__ +#define __ZXING_QRCODE_DECODER_DECODEDBITSTREAMPARSER_HPP__ + +#include "../../common/array.hpp" +#include "../../common/bitsource.hpp" +#include "../../common/characterseteci.hpp" +#include "../../common/counted.hpp" +#include "../../common/decoder_result.hpp" +#include "../../decodehints.hpp" +#include "../../errorhandler.hpp" +#include "mode.hpp" + + +namespace zxing { +namespace qrcode { + +class DecodedBitStreamParser { +public: + DecodedBitStreamParser() : outputCharset("UTF-8") {} + +private: + static char const ALPHANUMERIC_CHARS[]; + + string outputCharset; + // string outputCharset; + + char toAlphaNumericChar(size_t value, ErrorHandler& err_handler); + + void decodeHanziSegment(Ref bits, std::string& result, int count, + ErrorHandler& err_handler); + void decodeKanjiSegment(Ref bits, std::string& result, int count, + ErrorHandler& err_handler); + void decodeByteSegment(Ref bits, std::string& result, int count); + void decodeByteSegment(Ref bits_, std::string& result, int count, + zxing::common::CharacterSetECI* currentCharacterSetECI, + ArrayRef >& byteSegments, ErrorHandler& err_handler); + void decodeAlphanumericSegment(Ref bits, std::string& result, int count, + bool fc1InEffect, ErrorHandler& err_handler); + void decodeNumericSegment(Ref bits, std::string& result, int count, + ErrorHandler& err_handler); + + void append(std::string& ost, const char* bufIn, size_t nIn, ErrorHandler& err_handler); + void append(std::string& ost, std::string const& in, ErrorHandler& err_handler); + +public: + Ref decode(ArrayRef bytes, Version* version, + ErrorCorrectionLevel const& ecLevel, ErrorHandler& err_handler, + int iVersion = -1); + + // string getResultCharset(); +}; + +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DECODER_DECODEDBITSTREAMPARSER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoder.cpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoder.cpp new file mode 100644 index 0000000..4ebe475 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoder.cpp @@ -0,0 +1,223 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "decoder.hpp" +#include "../error_correction_level.hpp" +#include "../version.hpp" +#include "datablock.hpp" +#include "decoded_bit_stream_parser.hpp" +#include "qrcode_decoder_metadata.hpp" + +using zxing::DecoderResult; +using zxing::Ref; +using zxing::qrcode::Decoder; + +// VC++ +// The main class which implements QR Code decoding -- as opposed to locating +// and extracting the QR Code from an image. +using zxing::ArrayRef; +using zxing::BitMatrix; +using zxing::DetectorResult; +using zxing::ErrorHandler; + +Decoder::Decoder() : rsDecoder_(Ref(GF_QR_CODE_FIELD_256)) { + possibleVersion_ = 0; + possibleFix_ = 0; + decoderState_ = NOTSTART; +} + +// Convenience method that can decode a QR Code represented as a 2D array of +// booleans. "true" is taken to mean a black module. +Ref Decoder::decode(Ref bits, ErrorHandler &err_handler) { + string errMsg = ""; + + // Used for mirrored qrcode + int width = bits->getWidth(); + int height = bits->getHeight(); + + Ref bits2(new BitMatrix(width, height, bits->getPtr(), err_handler)); + if (err_handler.ErrCode()) return Ref(); + Ref rst = decode(bits, false, err_handler); + if (err_handler.ErrCode() || rst == NULL) { + errMsg = err_handler.ErrMsg(); + } else { + return rst; + } + + err_handler.Reset(); + Ref result = decode(bits2, true, err_handler); + if (err_handler.ErrCode()) { + return Ref(); + } else { + // Success! Notify the caller that the code was mirrored. + result->setOther(Ref(new QRCodeDecoderMetaData(true))); + return result; + } +}; + +Ref Decoder::decode(Ref bits, bool isMirror, ErrorHandler &err_handler) { + // Ref Decoder::decode(BitMatrixParser& parser) { + // Construct a parser and read version, error-correction level + BitMatrixParser parser(bits, err_handler); + if (err_handler.ErrCode()) return Ref(); + + if (isMirror == true) { + // 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(err_handler); + if (err_handler.ErrCode()) { + err_handler = zxing::ReaderErrorHandler("Decoder::decode mirror & no mirror"); + return Ref(); + } + + // Preemptively read the format information. + parser.readFormatInformation(err_handler); + if (err_handler.ErrCode()) return Ref(); + + /* + * 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(); + } + + decoderState_ = START; + possibleFix_ = 0; + Version *version = parser.readVersion(err_handler); + if (err_handler.ErrCode() || version == NULL) { + err_handler = ReaderErrorHandler("Decoder::decode mirror & no mirror"); + return Ref(); + } + decoderState_ = READVERSION; + float fixedPatternScore = estimateFixedPattern(bits, version, err_handler); + if (err_handler.ErrCode()) return Ref(); + + Ref formatInfo = parser.readFormatInformation(err_handler); + if (err_handler.ErrCode()) return Ref(); + ErrorCorrectionLevel &ecLevel = formatInfo->getErrorCorrectionLevel(); + + decoderState_ = READERRORCORRECTIONLEVEL; + + // Read codewords + ArrayRef codewords(parser.readCodewords(err_handler)); + if (err_handler.ErrCode()) { + err_handler = zxing::ReaderErrorHandler("Decoder::decode mirror & no mirror"); + return Ref(); + } + + decoderState_ = READCODEWORDSORRECTIONLEVEL; + possibleFix_ = fixedPatternScore; + + // Separate into data blocks + std::vector > dataBlocks( + DataBlock::getDataBlocks(codewords, version, ecLevel, err_handler)); + if (err_handler.ErrCode()) return Ref(); + + // Count total number of data bytes + int totalBytes = 0; + for (size_t i = 0; i < dataBlocks.size(); i++) { + totalBytes += dataBlocks[i]->getNumDataCodewords(); + } + ArrayRef resultBytes(totalBytes); + int resultOffset = 0; + + // Error-correct and copy data blocks together into a stream of bytes + for (size_t j = 0; j < dataBlocks.size(); j++) { + err_handler.Reset(); + Ref dataBlock(dataBlocks[j]); + ArrayRef codewordBytes = dataBlock->getCodewords(); + int numDataCodewords = dataBlock->getNumDataCodewords(); + + correctErrors(codewordBytes, numDataCodewords, err_handler); + if (err_handler.ErrCode()) return Ref(); + + for (int i = 0; i < numDataCodewords; i++) { + resultBytes[resultOffset++] = codewordBytes[i]; + } + } + + decoderState_ = FINISH; + // return DecodedBitStreamParser::decode(resultBytes, + DecodedBitStreamParser dbs_parser; + Ref rst = + dbs_parser.decode(resultBytes, version, ecLevel, err_handler, version->getVersionNumber()); + + if (err_handler.ErrCode()) return Ref(); + return rst; +} + +// Given data and error-correction codewords received, possibly corrupted by +// errors, attempts to correct the errors in-place using Reed-Solomon error +// correction.

codewordBytes: data and error correction codewords +// numDataCodewords: number of codewords that are data bytes +void Decoder::correctErrors(ArrayRef codewordBytes, int numDataCodewords, + ErrorHandler &err_handler) { + // First read into an arrya of ints + int numCodewords = codewordBytes->size(); + ArrayRef codewordInts(numCodewords); + for (int i = 0; i < numCodewords; i++) { + codewordInts[i] = codewordBytes[i] & 0xff; + } + int numECCodewords = numCodewords - numDataCodewords; + bool correctErrorsFinishished = false; + + rsDecoder_.decode(codewordInts, numECCodewords, err_handler); + if (err_handler.ErrCode()) { + return; + } + + correctErrorsFinishished = true; + + // 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 + if (correctErrorsFinishished) { + for (int i = 0; i < numDataCodewords; i++) { + codewordBytes[i] = (char)codewordInts[i]; + } + } +} + +unsigned int Decoder::getPossibleVersion() { return possibleVersion_; } + +float Decoder::estimateFixedPattern(Ref bits, zxing::qrcode::Version *version, + ErrorHandler &err_handler) { + Ref fixedPatternValue = version->buildFixedPatternValue(err_handler); + if (err_handler.ErrCode()) { + err_handler = zxing::ReaderErrorHandler("Decoder::decode mirror & no mirror"); + return -1.0; + } + Ref fixedPatternTemplate = version->buildFixedPatternTemplate(err_handler); + if (err_handler.ErrCode()) { + err_handler = zxing::ReaderErrorHandler("Decoder::decode mirror & no mirror"); + return -1.0; + } + + int iSum = 0; + int iCount = 0; + for (int i = 0; i < bits->getHeight(); ++i) { + for (int j = 0; j < bits->getWidth(); ++j) { + if (fixedPatternTemplate->get(i, j)) { + iSum++; + if (bits->get(i, j) == fixedPatternValue->get(i, j)) iCount++; + } + } + } + + float possbielFix = 2.0 * iCount / iSum - 1; + return possbielFix > 0 ? possbielFix : 0; +} diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoder.hpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoder.hpp new file mode 100644 index 0000000..3ce67f7 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/decoder.hpp @@ -0,0 +1,65 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DECODER_DECODER_HPP__ +#define __ZXING_QRCODE_DECODER_DECODER_HPP__ + +#include "../../common/array.hpp" +#include "../../common/bitmatrix.hpp" +#include "../../common/counted.hpp" +#include "../../common/decoder_result.hpp" +#include "../../common/detector_result.hpp" +#include "../../common/reedsolomon/reed_solomon_decoder.hpp" +#include "../../errorhandler.hpp" +#include "../version.hpp" +#include "bitmatrixparser.hpp" + +namespace zxing { +namespace qrcode { + +class Decoder { +public: + enum DecoderState { + NOTSTART = 19, + START = 20, + READVERSION = 21, + READERRORCORRECTIONLEVEL = 22, + READCODEWORDSORRECTIONLEVEL = 23, + FINISH = 24 + }; + +private: + DecoderState decoderState_; + float possibleFix_; + ReedSolomonDecoder rsDecoder_; + void correctErrors(ArrayRef bytes, int numDataCodewords, ErrorHandler& err_handler); + +public: + Decoder(); + Ref decode(Ref bits, ErrorHandler& err_handler); + +private: + Ref decode(Ref bits, bool isMirror, ErrorHandler& err_handler); + + float estimateFixedPattern(Ref bits, Version* version, ErrorHandler& err_handler); + +private: + unsigned int possibleVersion_; + +public: + unsigned int getPossibleVersion(); + DecoderState getState() { return decoderState_; } + float getPossibleFix() { return possibleFix_; } +}; + +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DECODER_DECODER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/mode.cpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/mode.cpp new file mode 100644 index 0000000..f96b63b --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/mode.cpp @@ -0,0 +1,91 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "mode.hpp" +#include "../../common/counted.hpp" +#include "../../zxing.hpp" +#include "../version.hpp" + +#include + +using std::ostringstream; +using zxing::qrcode::Mode; + +// VC++ +using zxing::ErrorHandler; +using zxing::qrcode::Version; + +Mode Mode::TERMINATOR(0, 0, 0, 0x00, "TERMINATOR"); +Mode Mode::NUMERIC(10, 12, 14, 0x01, "NUMERIC"); +Mode Mode::ALPHANUMERIC(9, 11, 13, 0x02, "ALPHANUMERIC"); +Mode Mode::STRUCTURED_APPEND(0, 0, 0, 0x03, "STRUCTURED_APPEND"); +Mode Mode::BYTE(8, 16, 16, 0x04, "BYTE"); +Mode Mode::ECI(0, 0, 0, 0x07, "ECI"); +Mode Mode::KANJI(8, 10, 12, 0x08, "KANJI"); +Mode Mode::FNC1_FIRST_POSITION(0, 0, 0, 0x05, "FNC1_FIRST_POSITION"); +Mode Mode::FNC1_SECOND_POSITION(0, 0, 0, 0x09, "FNC1_SECOND_POSITION"); +Mode Mode::HANZI(8, 10, 12, 0x0D, "HANZI"); + +// Mode::Mode(int cbv0_9, int cbv10_26, int cbv27, int /* bits */, char const* +// name) : +Mode::Mode(int cbv0_9, int cbv10_26, int cbv27, int bits, char const* name) + : characterCountBitsForVersions0To9_(cbv0_9), + characterCountBitsForVersions10To26_(cbv10_26), + // characterCountBitsForVersions27AndHigher_(cbv27), name_(name) { + characterCountBitsForVersions27AndHigher_(cbv27), + bits_(bits), + name_(name) {} + +Mode& Mode::forBits(int bits, ErrorHandler& err_handler) { + 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: + ostringstream s; + s << "Illegal mode bits: " << bits; + err_handler = zxing::ReaderErrorHandler(s.str().c_str()); + return TERMINATOR; + } +} + +int Mode::getCharacterCountBits(Version* version) const { + int number = version->getVersionNumber(); + if (number <= 9) { + return characterCountBitsForVersions0To9_; + } else if (number <= 26) { + return characterCountBitsForVersions10To26_; + } else { + return characterCountBitsForVersions27AndHigher_; + } +} + +int Mode::getBits() const { return bits_; } + +string zxing::qrcode::Mode::getName() const { return name_; } diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/mode.hpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/mode.hpp new file mode 100644 index 0000000..2b3b146 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/mode.hpp @@ -0,0 +1,52 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DECODER_MODE_HPP__ +#define __ZXING_QRCODE_DECODER_MODE_HPP__ + +#include "../../common/counted.hpp" +#include "../../errorhandler.hpp" +#include "../version.hpp" + +namespace zxing { +namespace qrcode { + +class Mode { +private: + int characterCountBitsForVersions0To9_; + int characterCountBitsForVersions10To26_; + int characterCountBitsForVersions27AndHigher_; + int bits_; + std::string name_; + + Mode(int cbv0_9, int cbv10_26, int cbv27, int bits, char const* name); + +public: + static Mode TERMINATOR; + static Mode NUMERIC; + static Mode ALPHANUMERIC; + static Mode STRUCTURED_APPEND; + static Mode BYTE; + static Mode ECI; + static Mode KANJI; + static Mode FNC1_FIRST_POSITION; + static Mode FNC1_SECOND_POSITION; + static Mode HANZI; + + static Mode& forBits(int bits, ErrorHandler& err_handler); + // int getCharacterCountBits(Version *version); + int getCharacterCountBits(Version* version) const; + int getBits() const; + string getName() const; +}; +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DECODER_MODE_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/decoder/qrcode_decoder_metadata.hpp b/modules/wechat_qrcode/src/zxing/qrcode/decoder/qrcode_decoder_metadata.hpp new file mode 100644 index 0000000..e771893 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/decoder/qrcode_decoder_metadata.hpp @@ -0,0 +1,64 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DECODER_QRCODEDECODERMETADATA_HPP__ +#define __ZXING_QRCODE_DECODER_QRCODEDECODERMETADATA_HPP__ + +#include "../../common/array.hpp" +#include "../../common/counted.hpp" +#include "../../resultpoint.hpp" + +// VC++ +// The main class which implements QR Code decoding -- as opposed to locating +// and extracting the QR Code from an image. + +namespace zxing { +namespace qrcode { + +/** + * 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() + */ +class QRCodeDecoderMetaData : public Counted { +private: + bool mirrored_; + +public: + explicit QRCodeDecoderMetaData(bool mirrored) : mirrored_(mirrored) {} + +public: + /** + * @return true if the QR Code was mirrored. + */ + bool isMirrored() { return mirrored_; }; + + /** + * Apply the result points' order correction due to mirroring. + * + * @param points Array of points to apply mirror correction to. + */ + void applyMirroredCorrection(ArrayRef >& points) { + if (!mirrored_ || points->size() < 3) { + return; + } + Ref bottomLeft = points[0]; + points[0] = points[2]; + points[2] = bottomLeft; + // No need to 'fix' top-left and alignment pattern. + }; +}; + +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DECODER_QRCODEDECODERMETADATA_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern.cpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern.cpp new file mode 100644 index 0000000..deefeeb --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern.cpp @@ -0,0 +1,42 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "alignment_pattern.hpp" +using zxing::Ref; +using zxing::qrcode::AlignmentPattern; +namespace zxing { +namespace qrcode { +AlignmentPattern::AlignmentPattern(float posX, float posY, float estimatedModuleSize) + : ResultPoint(posX, posY), 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. +bool AlignmentPattern::aboutEquals(float moduleSize, float i, float j) const { + if (abs(i - getY()) <= moduleSize && abs(j - getX()) <= moduleSize) { + float moduleSizeDiff = 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. +Ref AlignmentPattern::combineEstimate(float i, float j, + float newModuleSize) const { + float combinedX = (getX() + j) / 2.0f; + float combinedY = (getY() + i) / 2.0f; + float combinedModuleSize = (estimatedModuleSize_ + newModuleSize) / 2.0f; + Ref result(new AlignmentPattern(combinedX, combinedY, combinedModuleSize)); + return result; +} +} // namespace qrcode +} // namespace zxing \ No newline at end of file diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern.hpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern.hpp new file mode 100644 index 0000000..74a15e3 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern.hpp @@ -0,0 +1,34 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DETECTOR_ALIGNMENT_PATTERN_HPP_ +#define __ZXING_QRCODE_DETECTOR_ALIGNMENT_PATTERN_HPP_ + +#include "../../common/bitmatrix.hpp" +#include "../../resultpoint.hpp" +namespace zxing { +namespace qrcode { + +class AlignmentPattern : public ResultPoint { +private: + float estimatedModuleSize_; + +public: + AlignmentPattern(float posX, float posY, float estimatedModuleSize); + bool aboutEquals(float moduleSize, float i, float j) const; + float getModuleSize() { return estimatedModuleSize_; }; + + Ref combineEstimate(float i, float j, float newModuleSize) const; +}; + +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DETECTOR_ALIGNMENT_PATTERN_HPP_ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern_finder.cpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern_finder.cpp new file mode 100644 index 0000000..072371d --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern_finder.cpp @@ -0,0 +1,231 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "alignment_pattern_finder.hpp" + +using zxing::ErrorHandler; +using zxing::ReaderErrorHandler; +using zxing::Ref; +using zxing::qrcode::AlignmentPattern; +using zxing::qrcode::AlignmentPatternFinder; +using zxing::qrcode::FinderPattern; + +// VC++ +// 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 duplicat some code. This class is +// thread-safe but not reentrant. Each thread must allocate its own object. +using zxing::BitMatrix; + +// Creates a finder that will look in a portion of the whole image. +AlignmentPatternFinder::AlignmentPatternFinder(Ref image, int startX, int startY, + int width, int height, float moduleSize) + : image_(image), + possibleCenters_(new vector()), + startX_(startX), + startY_(startY), + width_(width), + height_(height), + moduleSize_(moduleSize) {} + +AlignmentPatternFinder::AlignmentPatternFinder(Ref image, float moduleSize) + : image_(image), + moduleSize_(moduleSize) {} + +// 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. +Ref AlignmentPatternFinder::find(ErrorHandler &err_handler) { + int maxJ = startX_ + width_; + int middleI = startY_ + (height_ >> 1); + // 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 + vector stateCount(3, 0); + for (int iGen = 0; iGen < height_; iGen++) { + // Search from middle outwards + int i = middleI + ((iGen & 0x01) == 0 ? ((iGen + 1) >> 1) : -((iGen + 1) >> 1)); + 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[currentState]++; + } else { // Counting white pixels + if (currentState == 2) { // A winner? + if (foundPatternCross(stateCount)) { // Yes + Ref confirmed(handlePossibleCenter(stateCount, i, j)); + if (confirmed != 0) { + 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)) { + Ref confirmed(handlePossibleCenter(stateCount, i, maxJ)); + if (confirmed != 0) { + return confirmed; + } + } + } + // Nothing we saw was observed and confirmed twice. If we had any guess at + // all, return it. + if (possibleCenters_->size() > 0) { + Ref center((*possibleCenters_)[0]); + return center; + } + err_handler = ReaderErrorHandler("Could not find alignment pattern"); + return Ref(); +} + + +// 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. +float AlignmentPatternFinder::centerFromEnd(vector &stateCount, int end) { + return (float)(end - stateCount[2]) - stateCount[1] / 2.0f; +} + + + +bool AlignmentPatternFinder::foundPatternCross(vector &stateCount) { + float maxVariance = moduleSize_ / 2.0f; + for (int i = 0; i < 3; i++) { + if (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. return vertical +// center of alignment pattern, or nan() if not found startI: row where an +// alignment pattern was detected centerJ: center of the section that appears to +// cross an alignment pattern +// maxCount: maximum reasonable number of modules that should be observed in any +// reading state, +// based on the results of the horizontal scan +float AlignmentPatternFinder::crossCheckVertical(int startI, int centerJ, int maxCount, + int originalStateCountTotal) { + // This is slightly faster than using the Ref. Efficiency is important here + BitMatrix &matrix = *image_; + + int maxI = matrix.getHeight(); + vector stateCount(3, 0); + // Start counting up from center + int i = startI; + while (i >= 0 && matrix.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 nan(); + } + while (i >= 0 && !matrix.get(centerJ, i) && stateCount[0] <= maxCount) { + stateCount[0]++; + i--; + } + if (stateCount[0] > maxCount) { + return nan(); + } + + // Now also count down from center + i = startI + 1; + while (i < maxI && matrix.get(centerJ, i) && stateCount[1] <= maxCount) { + stateCount[1]++; + i++; + } + if (i == maxI || stateCount[1] > maxCount) { + return nan(); + } + while (i < maxI && !matrix.get(centerJ, i) && stateCount[2] <= maxCount) { + stateCount[2]++; + i++; + } + if (stateCount[2] > maxCount) { + return nan(); + } + + int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2]; + if (5 * abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { + return nan(); + } + return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : 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. return {@link +// AlignmentPattern} if we have found the same pattern twice, or null if not i: +// row where alignment pattern may be found j: end of possible alignment pattern +// in row +Ref AlignmentPatternFinder::handlePossibleCenter(vector &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 (!isnan(centerI)) { + float estimatedModuleSize = (float)(stateCount[0] + stateCount[1] + stateCount[2]) / 3.0f; + int max = possibleCenters_->size(); + for (int index = 0; index < max; index++) { + Ref center((*possibleCenters_)[index]); + // 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 *tmp = new AlignmentPattern(centerJ, centerI, estimatedModuleSize); + tmp->retain(); + possibleCenters_->push_back(tmp); + } + Ref result; + return result; +} + + +AlignmentPatternFinder::~AlignmentPatternFinder() { + for (int i = 0; i < int(possibleCenters_->size()); i++) { + (*possibleCenters_)[i]->release(); + (*possibleCenters_)[i] = 0; + } + delete possibleCenters_; +} diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern_finder.hpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern_finder.hpp new file mode 100644 index 0000000..ace3064 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/alignment_pattern_finder.hpp @@ -0,0 +1,60 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DETECTOR_ALIGNMENT_PATTERN_FINDER_HPP_ +#define __ZXING_QRCODE_DETECTOR_ALIGNMENT_PATTERN_FINDER_HPP_ + +#include "../../common/bitmatrix.hpp" +#include "../../common/counted.hpp" +#include "../../errorhandler.hpp" +#include "alignment_pattern.hpp" +#include "finder_pattern.hpp" + +namespace zxing { +namespace qrcode { + +class AlignmentPatternFinder : public Counted { +private: + static int CENTER_QUORUM; + static int MIN_SKIP; + static int MAX_MODULES; + + Ref image_; + std::vector *possibleCenters_; + + int startX_; + int startY_; + int width_; + int height_; + float moduleSize_; + static float centerFromEnd(std::vector &stateCount, int end); + float crossCheckVertical(int startI, int centerJ, int maxCount, int originalStateCountTotal); + + +public: + AlignmentPatternFinder(Ref image, int startX, int startY, int width, int height, + float moduleSize); + AlignmentPatternFinder(Ref image, float moduleSize); + ~AlignmentPatternFinder(); + + Ref find(ErrorHandler &err_handler); + bool foundPatternCross(std::vector &stateCount); + Ref handlePossibleCenter(std::vector &stateCount, int i, int j); + + +private: + AlignmentPatternFinder(const AlignmentPatternFinder &); + AlignmentPatternFinder &operator=(const AlignmentPatternFinder &); + +}; +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DETECTOR_ALIGNMENT_PATTERN_FINDER_HPP_ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/detector.cpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/detector.cpp new file mode 100644 index 0000000..e22a852 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/detector.cpp @@ -0,0 +1,1065 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "detector.hpp" +#include +#include "../../common/grid_sampler.hpp" +#include "../../common/mathutils.hpp" +#include "../../decodehints.hpp" +#include "../version.hpp" +#include "alignment_pattern.hpp" +#include "alignment_pattern_finder.hpp" +#include "finder_pattern.hpp" +#include "finder_pattern_finder.hpp" +#include "opencv2/core.hpp" + +using zxing::BitMatrix; +using zxing::DetectorResult; +using zxing::ErrorHandler; +using zxing::PerspectiveTransform; +using zxing::Ref; +using zxing::common::MathUtils; +using zxing::qrcode::AlignmentPattern; +using zxing::qrcode::Detector; +using zxing::qrcode::FinderPattern; + +// VC++ +using zxing::DecodeHints; +using zxing::ResultPoint; +using zxing::UnicomBlock; +using zxing::qrcode::FinderPatternFinder; +using zxing::qrcode::FinderPatternInfo; +using zxing::qrcode::PatternResult; + +// Encapsulates logic that can detect a QR Code in an image, +// even if the QR Code is rotated or skewed, or partially obscured. +Detector::Detector(Ref image, Ref block) : image_(image), block_(block) { + detectorState_ = START; + possiblePatternResults_.clear(); +} + +Ref Detector::getImage() const { return image_; } + +// Detects a QR Code in an image +void Detector::detect(DecodeHints const &hints, ErrorHandler &err_handler) { + FinderPatternFinder finder(image_, block_); + std::vector > finderInfos = finder.find(hints, err_handler); + if (err_handler.ErrCode()) return; + + // Get all possible results + possiblePatternResults_.clear(); + + for (size_t i = 0; i < finderInfos.size(); i++) { + Ref result(new PatternResult(finderInfos[i])); + result->possibleVersion = 0; + result->possibleFix = 0.0f; + result->possibleModuleSize = 0.0f; + + possiblePatternResults_.push_back(result); + } + detectorState_ = FINDFINDERPATTERN; +} + +int Detector::getPossibleAlignmentCount(int idx) { + if (idx >= int(possiblePatternResults_.size())) { + return -1; + } + + ErrorHandler err_handler; + // If it is first time to get, process it now + if (possiblePatternResults_[idx]->possibleAlignmentPatterns.size() == 0) { + Ref result = + processFinderPatternInfo(possiblePatternResults_[idx]->finderPatternInfo, err_handler); + if (err_handler.ErrCode()) return -1; + + possiblePatternResults_[idx] = result; + } + + return possiblePatternResults_[idx]->possibleAlignmentPatterns.size(); +} + +Ref Detector::getResultViaAlignment(int patternIdx, int alignmentIdx, + int possibleDimension, + ErrorHandler &err_handler) { + if (patternIdx >= int(possiblePatternResults_.size()) || patternIdx < 0) { + return Ref(NULL); + } + + if (alignmentIdx >= + int(possiblePatternResults_[patternIdx]->possibleAlignmentPatterns.size()) || + alignmentIdx < 0) { + return Ref(NULL); + } + + // Default is the dimension + if (possibleDimension <= 0) { + possibleDimension = possiblePatternResults_[patternIdx]->getDimension(); + } + + Ref topLeft( + possiblePatternResults_[patternIdx]->finderPatternInfo->getTopLeft()); + Ref topRight( + possiblePatternResults_[patternIdx]->finderPatternInfo->getTopRight()); + Ref bottomLeft( + possiblePatternResults_[patternIdx]->finderPatternInfo->getBottomLeft()); + + Ref alignment( + possiblePatternResults_[patternIdx]->possibleAlignmentPatterns[alignmentIdx]); + Ref transform = + createTransform(topLeft, topRight, bottomLeft, alignment, possibleDimension); + Ref bits(sampleGrid(image_, possibleDimension, transform, err_handler)); + if (err_handler.ErrCode()) return Ref(); + + ArrayRef > corrners(new Array >(4)); + vector points(8, 0.0f); + points[0] = 0.0f; + points[1] = possibleDimension; // bottomLeft + points[2] = 0.0f; + points[3] = 0.0f; // topLeft + points[4] = possibleDimension; + points[5] = 0.0f; // topRight + points[6] = possibleDimension; + points[7] = possibleDimension; // bottomRight + transform->transformPoints(points); + corrners[0].reset(Ref(new FinderPattern(points[0], points[1], 0))); + corrners[1].reset(Ref(new FinderPattern(points[2], points[3], 0))); + corrners[2].reset(Ref(new FinderPattern(points[4], points[5], 0))); + corrners[3].reset(Ref(new FinderPattern(points[6], points[7], 0))); + + Ref result(new DetectorResult(bits, corrners, possibleDimension)); + return result; +} + +bool Detector::hasSameResult(vector > possibleAlignmentPatterns, + Ref alignmentPattern) { + float moduleSize = alignmentPattern->getModuleSize() / 5.0; + + if (moduleSize < 1.0) { + moduleSize = 1.0; + } + + for (size_t i = 0; i < possibleAlignmentPatterns.size(); i++) { + if (possibleAlignmentPatterns[i]->aboutEquals(moduleSize, alignmentPattern->getY(), + alignmentPattern->getX())) { + return true; + } + } + return false; +} + +Ref Detector::getNearestAlignmentPattern(int tryFindRange, float moduleSize, + int estAlignmentX, int estAlignmentY) { + Ref alignmentPattern; + + ErrorHandler err_handler; + for (int i = 2; i <= tryFindRange; i <<= 1) { + err_handler.Reset(); + alignmentPattern = + findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, (float)i, err_handler); + if (err_handler.ErrCode() == 0) break; + } + + return alignmentPattern; +} + +Ref Detector::processFinderPatternInfo(Ref info, + ErrorHandler &err_handler) { + Ref topLeft(info->getTopLeft()); + Ref topRight(info->getTopRight()); + Ref bottomLeft(info->getBottomLeft()); + + Ref result(new PatternResult(info)); + result->finderPatternInfo = info; + result->possibleAlignmentPatterns.clear(); + + float moduleSizeX_ = calculateModuleSizeOneWay( + topLeft, topRight, topLeft->getHorizontalCheckState(), topRight->getHorizontalCheckState()); + float moduleSizeY_ = calculateModuleSizeOneWay( + topLeft, bottomLeft, topLeft->getVerticalCheckState(), bottomLeft->getVerticalCheckState()); + + if (moduleSizeX_ < 1.0f || moduleSizeY_ < 1.0f) { + err_handler = ReaderErrorHandler("bad midule size"); + return Ref(); + } + + float moduleSize = (moduleSizeX_ + moduleSizeY_) / 2.0f; + + if (moduleSize > topLeft->getEstimatedModuleSize() * 1.05 && + moduleSize > topRight->getEstimatedModuleSize() * 1.05 && + moduleSize > bottomLeft->getEstimatedModuleSize() * 1.05) { + moduleSize = (topLeft->getEstimatedModuleSize() + topRight->getEstimatedModuleSize() + + bottomLeft->getEstimatedModuleSize()) / + 3; + moduleSizeX_ = moduleSize; + moduleSizeY_ = moduleSize; + } + result->possibleModuleSize = moduleSize; + + if (moduleSize < 1.0f) { + err_handler = ReaderErrorHandler("bad midule size"); + return Ref(); + } + int dimension = computeDimension(topLeft, topRight, bottomLeft, moduleSizeX_, moduleSizeY_); + Version *provisionalVersion = NULL; + + // Try demension around if it cannot get a version + int dimensionDiff[5] = {0, 1, -1, 2, -2}; + + int oriDimension = dimension; + + for (int i = 0; i < 5; i++) { + err_handler.Reset(); + dimension = oriDimension + dimensionDiff[i]; + + provisionalVersion = Version::getProvisionalVersionForDimension(dimension, err_handler); + if (err_handler.ErrCode() == 0) break; + } + if (provisionalVersion == NULL) { + err_handler = zxing::ReaderErrorHandler("Cannot get version number"); + return Ref(); + } + + result->possibleDimension = dimension; + + result->possibleVersion = provisionalVersion->getVersionNumber(); + + int modulesBetweenFPCenters = provisionalVersion->getDimensionForVersion(err_handler) - 7; + if (err_handler.ErrCode()) return Ref(); + + Ref alignmentPattern; + + // 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 / (float)modulesBetweenFPCenters; + int estAlignmentX = + (int)(topLeft->getX() + correctionToTopLeft * (bottomRightX - topLeft->getX())); + int estAlignmentY = + (int)(topLeft->getY() + correctionToTopLeft * (bottomRightY - topLeft->getY())); + + Ref estimateCenter( + new AlignmentPattern(estAlignmentX, estAlignmentY, moduleSize)); + + bool foundFitLine = false; + Ref fitLineCenter; + + fitLineCenter = + findAlignmentWithFitLine(topLeft, topRight, bottomLeft, moduleSize, err_handler); + if (err_handler.ErrCode() == 0) { + if (fitLineCenter != NULL && + MathUtils::isInRange(fitLineCenter->getX(), fitLineCenter->getY(), image_->getWidth(), + image_->getHeight())) { + foundFitLine = true; + } + } + err_handler.Reset(); + + Ref fitAP, estAP; + + // Anything above version 1 has an alignment pattern + if (provisionalVersion->getAlignmentPatternCenters().size()) { + // if(alignmentPattern!=NULL&&alignmentPattern->getX()>0&&alignmentPattern->getY()>0){ + int tryFindRange = provisionalVersion->getDimensionForVersion(err_handler) / 2; + if (err_handler.ErrCode()) return Ref(); + + if (foundFitLine == true) { + fitAP = getNearestAlignmentPattern(tryFindRange, moduleSize, fitLineCenter->getX(), + fitLineCenter->getY()); + + if (fitAP != NULL && !hasSameResult(result->possibleAlignmentPatterns, fitAP)) + // if (fitAP != NULL && + // !hasSameResult(result->possibleAlignmentPatterns, fitAP) && + // checkConvexQuadrilateral(topLeft, topRight, bottomLeft, fitAP)) + { + result->possibleAlignmentPatterns.push_back(fitAP); + } + } + + estAP = getNearestAlignmentPattern(tryFindRange, moduleSize, estimateCenter->getX(), + estimateCenter->getY()); + + if (estAP != NULL && !hasSameResult(result->possibleAlignmentPatterns, estAP)) + // if (estAP != NULL && + // !hasSameResult(result->possibleAlignmentPatterns, estAP) && + // checkConvexQuadrilateral(topLeft, topRight, bottomLeft, estAP)) + { + result->possibleAlignmentPatterns.push_back(estAP); + } + } + + // Any way use the fit line result + if (foundFitLine == true && !hasSameResult(result->possibleAlignmentPatterns, fitLineCenter)) { + float alignmentX = fitLineCenter->getX(); + float alignmentY = fitLineCenter->getY(); + fixAlignmentPattern(alignmentX, alignmentY, moduleSize); + Ref fitLineCenterFixed = + Ref(new AlignmentPattern(alignmentX, alignmentY, moduleSize)); + if (!hasSameResult(result->possibleAlignmentPatterns, fitLineCenterFixed)) { + result->possibleAlignmentPatterns.push_back(fitLineCenterFixed); + } + + if (!hasSameResult(result->possibleAlignmentPatterns, fitLineCenter)) { + result->possibleAlignmentPatterns.push_back(fitLineCenter); + } + } + + if (!hasSameResult(result->possibleAlignmentPatterns, estimateCenter)) { + float alignmentX = estimateCenter->getX(); + float alignmentY = estimateCenter->getY(); + fixAlignmentPattern(alignmentX, alignmentY, moduleSize); + Ref estimateCenterFixed = + Ref(new AlignmentPattern(alignmentX, alignmentY, moduleSize)); + if (!hasSameResult(result->possibleAlignmentPatterns, estimateCenterFixed)) { + result->possibleAlignmentPatterns.push_back(estimateCenterFixed); + } + + if (!hasSameResult(result->possibleAlignmentPatterns, estimateCenter)) { + result->possibleAlignmentPatterns.push_back(estimateCenter); + } + } + Ref NoneEstimateCenter = + Ref(new AlignmentPattern(0, 0, moduleSize)); + result->possibleAlignmentPatterns.push_back(NoneEstimateCenter); + + if (result->possibleAlignmentPatterns.size() > 0) { + result->confirmedAlignmentPattern = result->possibleAlignmentPatterns[0]; + } + detectorState_ = FINDALIGNPATTERN; + + return result; +} + +// Computes an average estimated module size based on estimated derived from the +// positions of the three finder patterns. +float Detector::calculateModuleSize(Ref topLeft, Ref topRight, + Ref bottomLeft) { + // Take the average + return (calculateModuleSizeOneWay(topLeft, topRight, NORMAL, NORMAL) + + calculateModuleSizeOneWay(topLeft, bottomLeft, NORMAL, NORMAL)) / + 2.0f; +} + +// Estimates module size based on two finder patterns +// it uses sizeOfBlackWhiteBlackRunBothWays() to figure the width of each, +// measuring along the axis between their centers. +float Detector::calculateModuleSizeOneWay(Ref pattern, Ref otherPattern, + int patternState, int otherPatternState) { + float moduleSizeEst1 = sizeOfBlackWhiteBlackRunBothWays( + (int)pattern->getX(), (int)pattern->getY(), (int)otherPattern->getX(), + (int)otherPattern->getY(), patternState, false); + float moduleSizeEst2 = sizeOfBlackWhiteBlackRunBothWays( + (int)otherPattern->getX(), (int)otherPattern->getY(), (int)pattern->getX(), + (int)pattern->getY(), otherPatternState, true); + if (zxing::isnan(moduleSizeEst1)) { + return moduleSizeEst2 / 7.0f; + } + if (zxing::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; +} + +// 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. +float Detector::sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY, + int patternState, bool isReverse) { + float result1 = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY); + float result = 0.0; + // 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 = (float)fromX / (float)(fromX - otherToX); + otherToX = 0; + } else if (otherToX >= (int)image_->getWidth()) { + scale = (float)(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 = (float)fromY / (float)(fromY - otherToY); + otherToY = 0; + } else if (otherToY >= (int)image_->getHeight()) { + scale = (float)(image_->getHeight() - 1 - fromY) / (float)(otherToY - fromY); + otherToY = image_->getHeight() - 1; + } + otherToX = (int)(fromX + (otherToX - fromX) * scale); + + float result2 = sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY); + + if (patternState == FinderPattern::HORIZONTAL_STATE_LEFT_SPILL || + patternState == FinderPattern::VERTICAL_STATE_UP_SPILL) { + if (!isReverse) + result = result1 * 2; + else + result = result2 * 2; + } else if (patternState == FinderPattern::HORIZONTAL_STATE_RIGHT_SPILL || + patternState == FinderPattern::VERTICAL_STATE_DOWN_SPILL) { + if (!isReverse) + result = result2 * 2; + else + result = result1 * 2; + } else { + result = result1 + result2; + } + // Middle pixel is double-counted this way; subtract 1 + return result - 1.0f; +} + +Ref Detector::sampleGrid(Ref image, int dimension, + Ref transform, + ErrorHandler &err_handler) { + GridSampler &sampler = GridSampler::getInstance(); + // return sampler.sampleGrid(image, dimension, transform); + Ref bits = sampler.sampleGrid(image, dimension, transform, err_handler); + if (err_handler.ErrCode()) return Ref(); + return bits; +} + +// 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. +float Detector::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 + bool steep = abs(toY - fromY) > abs(toX - fromX); + if (steep) { + // swap(fromX,fromY) + int temp = fromX; + fromX = fromY; + fromY = temp; + // swap(toX,toY) + temp = toX; + toX = toY; + toY = temp; + } + + int dx = abs(toX - fromX); + int dy = abs(toY - fromY); + int error = -dx >> 1; + 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 nan(); +} + +// Attempts to locate an alignment pattern in a limited region of the image, +// which is guessed to contain it. +Ref Detector::findAlignmentInRegion(float overallEstModuleSize, int estAlignmentX, + int estAlignmentY, float allowanceFactor, + ErrorHandler &err_handler) { + // Look for an alignment pattern (3 modules in size) around where it should + // be + int allowance = (int)(allowanceFactor * overallEstModuleSize); + int alignmentAreaLeftX = max(0, estAlignmentX - allowance); + int alignmentAreaRightX = min((int)(image_->getWidth() - 1), estAlignmentX + allowance); + if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) { + err_handler = ReaderErrorHandler("region too small to hold alignment pattern"); + return Ref(); + } + int alignmentAreaTopY = max(0, estAlignmentY - allowance); + int alignmentAreaBottomY = min((int)(image_->getHeight() - 1), estAlignmentY + allowance); + if (alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize * 3) { + err_handler = ReaderErrorHandler("region too small to hold alignment pattern"); + return Ref(); + } + + AlignmentPatternFinder alignmentFinder( + image_, alignmentAreaLeftX, alignmentAreaTopY, alignmentAreaRightX - alignmentAreaLeftX, + alignmentAreaBottomY - alignmentAreaTopY, overallEstModuleSize); + + Ref ap = alignmentFinder.find(err_handler); + if (err_handler.ErrCode()) return Ref(); + return ap; + +} + +Ref Detector::findAlignmentWithFitLine(Ref topLeft, + Ref topRight, + Ref bottomLeft, + float moduleSize, + ErrorHandler &err_handler) { + float alignmentX = 0.0f, alignmentY = 0.0f; + int imgWidth = image_->getWidth(); + int imgHeight = image_->getHeight(); + Rect bottomLeftRect, topRightRect; + double rectSize = moduleSize * 7; + bottomLeftRect.x = + (bottomLeft->getX() - rectSize / 2.0f) > 0 ? (bottomLeft->getX() - rectSize / 2.0f) : 0; + bottomLeftRect.y = + (bottomLeft->getY() - rectSize / 2.0f) > 0 ? (bottomLeft->getY() - rectSize / 2.0f) : 0; + bottomLeftRect.width = (bottomLeft->getX() - bottomLeftRect.x) * 2; + if (bottomLeftRect.x + bottomLeftRect.width > imgWidth) + bottomLeftRect.width = imgWidth - bottomLeftRect.x; + bottomLeftRect.height = (bottomLeft->getY() - bottomLeftRect.y) * 2; + if (bottomLeftRect.y + bottomLeftRect.height > imgHeight) + bottomLeftRect.height = imgHeight - bottomLeftRect.y; + + topRightRect.x = + (topRight->getX() - rectSize / 2.0f) > 0 ? (topRight->getX() - rectSize / 2.0f) : 0; + topRightRect.y = + (topRight->getY() - rectSize / 2.0f) > 0 ? (topRight->getY() - rectSize / 2.0f) : 0; + topRightRect.width = (topRight->getX() - topRightRect.x) * 2; + if (topRightRect.x + topRightRect.width > imgWidth) + topRightRect.width = imgWidth - topRightRect.x; + topRightRect.height = (topRight->getY() - topRightRect.y) * 2; + if (topRightRect.y + topRightRect.height > imgHeight) + topRightRect.height = imgHeight - topRightRect.y; + + vector > topRightPoints; + vector > bottomLeftPoints; + + findPointsForLine(topLeft, topRight, bottomLeft, topRightRect, bottomLeftRect, topRightPoints, + bottomLeftPoints, moduleSize); + + int a1; + float k1, b1; + int fitResult = fitLine(topRightPoints, k1, b1, a1); + if (fitResult < 0) { + err_handler = ReaderErrorHandler("Cannot find a valid divide for line fit"); + return Ref(); + } + + int a2; + float k2, b2; + int fitResult2 = fitLine(bottomLeftPoints, k2, b2, a2); + if (fitResult2 < 0) { + err_handler = ReaderErrorHandler("Cannot find a valid divide for line fit"); + return Ref(); + } + + int hasResult = 1; + if (a1 == 0) { + if (a2 == 0) { + hasResult = 0; + } else { + alignmentX = -b1; + alignmentY = b2 - b1 * k2; + } + } else { + if (a2 == 0) { + alignmentX = -b2; + alignmentY = b1 - b2 * k1; + } else { + if (k1 == k2) { + hasResult = 0; + } else { + alignmentX = (b2 - b1) / (k1 - k2); + alignmentY = k1 * alignmentX + b1; + } + } + } + + // Donot have a valid divide + if (hasResult == 0) { + err_handler = ReaderErrorHandler("Cannot find a valid divide for line fit"); + return Ref(); + } + Ref result(new AlignmentPattern(alignmentX, alignmentY, moduleSize)); + return result; +} + +void Detector::fixAlignmentPattern(float &alignmentX, float &alignmentY, float moduleSize) { + int imgWidth = image_->getWidth(); + int imgHeight = image_->getHeight(); + int maxFixStep = moduleSize * 2; + int fixStep = 0; + while (alignmentX < imgWidth && alignmentY < imgHeight && alignmentX > 0 && alignmentY > 0 && + !image_->get(alignmentX, alignmentY) && fixStep < maxFixStep) { + ++fixStep; + // Newest Version: The fix process is like this: + // 1 2 3 + // 4 0 5 + // 6 7 8 + for (int y = alignmentY - fixStep; y <= alignmentY + fixStep; y++) { + if (y == alignmentY - fixStep || y == alignmentY + fixStep) { + for (int x = alignmentX - fixStep; x <= alignmentX + fixStep; x++) { + if (x < imgWidth && y < imgHeight && x > 0 && y > 0 && image_->get(x, y)) { + alignmentX = x; + alignmentY = y; + return; + } + } + } else { + int x = alignmentX - fixStep; + if (x < imgWidth && y < imgHeight && x > 0 && y > 0 && image_->get(x, y)) { + alignmentX = x; + alignmentY = y; + return; + } + x = alignmentX + fixStep; + if (x < imgWidth && y < imgHeight && x > 0 && y > 0 && image_->get(x, y)) { + alignmentX = x; + alignmentY = y; + return; + } + } + } + } + + return; +} + +int Detector::fitLine(vector > &oldPoints, float &k, float &b, int &a) { + a = 1; + k = 0.0f; + b = 0.0f; + int old_num = oldPoints.size(); + if (old_num < 2) { + return -1; + } + float tolerance = 2.0f; + vector > fitPoints; + float pre_diff = -1; + for (vector >::iterator it = oldPoints.begin() + 1; it != oldPoints.end() - 1; + it++) { + float diff_x = 0.0f, diff_y = 0.0f, diff = 0.0f; + if (pre_diff < 0) { + diff_x = (*(it - 1))->getX() - (*it)->getX(); + diff_y = (*(it - 1))->getY() - (*it)->getY(); + diff = (diff_x * diff_x + diff_y * diff_y); + pre_diff = diff; + } + diff_x = (*(it + 1))->getX() - (*it)->getX(); + diff_y = (*(it + 1))->getY() - (*it)->getY(); + diff = (diff_x * diff_x + diff_y * diff_y); + if (pre_diff <= tolerance && diff <= tolerance) { + fitPoints.push_back(*(it)); + } + pre_diff = diff; + } + + int num = fitPoints.size(); + if (num < 2) return -1; + + double x = 0, y = 0, xx = 0, xy = 0, yy = 0, tem = 0; + for (int i = 0; i < num; i++) { + int point_x = fitPoints[i]->getX(); + int point_y = fitPoints[i]->getY(); + x += point_x; + y += point_y; + xx += point_x * point_x; + xy += point_x * point_y; + yy += point_y * point_y; + } + + tem = xx * num - x * x; + if (abs(tem) < 0.0000001) { + // Set b as average x + b = -x / num; + a = 0; + k = 1; + + return 1; + } + + k = (num * xy - x * y) / tem; + b = (y - k * x) / num; + a = 1; + if (abs(k) < 0.01) k = 0; + return 1; +} + +bool Detector::checkTolerance(Ref &topLeft, Ref &topRight, + Rect &topRightRect, double modelSize, Ref &p, int flag) { + int topLeftX = topLeft->getX(), topLeftY = topLeft->getY(), topRightX = topRight->getX(), + topRightY = topRight->getY(); + double left_right_k = 0.0f, left_right_b = 0.0f, left_right_b_tolerance, tolerance_b1 = 0.0f, + tolerance_b2 = 0.0f; + if (flag < 2) { + double tolerance_y1 = 0.0f, tolerance_y2 = 0.0f; + double tolerance_x = topRightRect.x; + if (flag == 1) tolerance_x = topRightRect.x + topRightRect.width; + if (topRightX != topLeftX) { + left_right_k = (topRightY - topLeftY) / (double)(topRightX - topLeftX); + left_right_b = (topRightY - left_right_k * topRightX); + double tmp_1 = modelSize * 2.5f; + double tmp_2 = tmp_1 * left_right_k; + + left_right_b_tolerance = sqrt(tmp_1 * tmp_1 + tmp_2 * tmp_2); + tolerance_b1 = left_right_b - left_right_b_tolerance; + tolerance_b2 = left_right_b + left_right_b_tolerance; + tolerance_y1 = left_right_k * tolerance_x + tolerance_b1; + tolerance_y2 = left_right_k * tolerance_x + tolerance_b2; + } else { + return false; + } + if (p->getY() < tolerance_y1 || p->getY() > tolerance_y2) return false; + return true; + } else { + double tolerance_x1 = 0.0f, tolerance_x2 = 0.0f; + if (topRightY != topLeftY) { + double tolerance_y = topRightRect.y; + if (flag == 3) tolerance_y = topRightRect.y + topRightRect.height; + left_right_k = (topRightX - topLeftX) / (double)(topRightY - topLeftY); + left_right_b = (topRightX - left_right_k * topRightY); + double tmp_1 = modelSize * 2.5f; + double tmp_2 = tmp_1 / left_right_k; + left_right_b_tolerance = sqrt(tmp_1 * tmp_1 + tmp_2 * tmp_2); + tolerance_b1 = left_right_b - left_right_b_tolerance; + tolerance_b2 = left_right_b + left_right_b_tolerance; + tolerance_x1 = left_right_k * tolerance_y + tolerance_b1; + tolerance_x2 = left_right_k * tolerance_y + tolerance_b2; + if (p->getX() < tolerance_x1 || p->getX() > tolerance_x2) return false; + return true; + } else { + return false; + } + } +} + +void Detector::findPointsForLine(Ref &topLeft, Ref &topRight, + Ref &bottomLeft, Rect topRightRect, + Rect bottomLeftRect, vector > &topRightPoints, + vector > &bottomLeftPoints, float modelSize) { + int topLeftX = topLeft->getX(), topLeftY = topLeft->getY(), topRightX = topRight->getX(), + topRightY = topRight->getY(); + if (!topRightPoints.empty()) topRightPoints.clear(); + if (!bottomLeftPoints.empty()) bottomLeftPoints.clear(); + + int xMin = 0; + int xMax = 0; + int yMin = 0; + int yMax = 0; + + int imgWidth = image_->getWidth(); + int imgHeight = image_->getHeight(); + + // [-45, 45] or [135, 180) or [-180, -45) + if (topLeftY == topRightY || abs((topRightX - topLeftX) / (topRightY - topLeftY)) >= 1) { + if (topLeftX < topRightX) { + xMin = topRightRect.x; + xMax = topRightRect.x + modelSize * 2; + yMin = topRightRect.y + modelSize; + yMax = topRightRect.y - modelSize + topRightRect.height; + // [-45, 45] TopRight: left, black->white points; BottomLeft: top, black->white points + MathUtils::getRangeValues(xMin, xMax, 0, imgWidth - 1); + MathUtils::getRangeValues(yMin, yMax, 0, imgHeight - 1); + + for (int i = yMin; i < yMax; i++) { + for (int j = xMin; j < xMax; j++) { + // left->right, black->white + if (image_->get(j, i) && !image_->get(j + 1, i)) { + Ref topRightPoint(new ResultPoint(j, i)); + if (checkTolerance(topLeft, topRight, topRightRect, modelSize, + topRightPoint, 0)) { + topRightPoints.push_back(topRightPoint); + break; + } + } + } + } + + xMin = bottomLeftRect.x + modelSize; + xMax = bottomLeftRect.x - modelSize + bottomLeftRect.width; + yMin = bottomLeftRect.y; + yMax = bottomLeftRect.y + 2 * modelSize; + + MathUtils::getRangeValues(xMin, xMax, 0, imgWidth - 1); + MathUtils::getRangeValues(yMin, yMax, 0, imgHeight - 1); + + for (int j = xMin; j < xMax; j++) { + for (int i = yMin; i < yMax; i++) { + // top to down, black->white + if (image_->get(j, i) && !image_->get(j, i + 1)) { + Ref bottomLeftPoint(new ResultPoint(j, i)); + if (checkTolerance(topLeft, bottomLeft, bottomLeftRect, modelSize, + bottomLeftPoint, 2)) { + bottomLeftPoints.push_back(bottomLeftPoint); + break; + } + } + } + } + } else { + // white->black points + xMin = topRightRect.x + topRightRect.width - 2 * modelSize; + xMax = topRightRect.x + topRightRect.width; + yMin = topRightRect.y + modelSize; + yMax = topRightRect.y - modelSize + topRightRect.height; + // [135, 180) or [-180, -45) TopRight: right, white->black points; BottomLeft: bottom, + MathUtils::getRangeValues(xMin, xMax, 0, imgWidth - 1); + MathUtils::getRangeValues(yMin, yMax, 0, imgHeight - 1); + + for (int i = yMin; i < yMax; i++) { + for (int j = xMin; j < xMax; j++) { + // left->right, white->black + if (!image_->get(j, i) && image_->get(j + 1, i)) { + Ref topRightPoint(new ResultPoint(j, i)); + if (checkTolerance(topLeft, topRight, topRightRect, modelSize, + topRightPoint, 1)) { + topRightPoints.push_back(topRightPoint); + break; + } + } + } + } + + xMin = bottomLeftRect.x + modelSize; + xMax = bottomLeftRect.x - modelSize + bottomLeftRect.width; + yMin = bottomLeftRect.y + bottomLeftRect.height - 2 * modelSize; + yMax = bottomLeftRect.y + bottomLeftRect.height; + + MathUtils::getRangeValues(xMin, xMax, 0, imgWidth - 1); + MathUtils::getRangeValues(yMin, yMax, 0, imgHeight - 1); + + for (int j = xMin; j < xMax; j++) { + for (int i = yMin; i < yMax; i++) { + // top to down, white->black + if (!image_->get(j, i) && image_->get(j, i + 1)) { + Ref bottomLeftPoint(new ResultPoint(j, i)); + if (checkTolerance(topLeft, bottomLeft, bottomLeftRect, modelSize, + bottomLeftPoint, 3)) { + bottomLeftPoints.push_back(bottomLeftPoint); + break; + } + } + } + } + } + } else { + // (45, 135) or (-45, -135) + // (45, 135) TopRight: top, black->white; BottomRight: right, black->white + if (topLeftY < topRightY) { + xMin = topRightRect.x + modelSize; + xMax = topRightRect.x - modelSize + topRightRect.width; + yMin = topRightRect.y; + yMax = topRightRect.y + 2 * modelSize; + + MathUtils::getRangeValues(xMin, xMax, 0, imgWidth - 1); + MathUtils::getRangeValues(yMin, yMax, 0, imgHeight - 1); + + for (int j = xMin; j < xMax; j++) { + for (int i = yMin; i < yMax; i++) { + // top to down, black->white + if (image_->get(j, i) && !image_->get(j, i + 1)) { + Ref topRightPoint(new ResultPoint(j, i)); + if (checkTolerance(topLeft, topRight, topRightRect, modelSize, + topRightPoint, 2)) { + topRightPoints.push_back(topRightPoint); + break; + } + } + } + } + + xMin = topRightRect.x + topRightRect.width - 2 * modelSize; + xMax = topRightRect.x + topRightRect.width; + yMin = topRightRect.y + modelSize; + yMax = topRightRect.y - modelSize + topRightRect.height; + + MathUtils::getRangeValues(xMin, xMax, 0, imgWidth - 1); + MathUtils::getRangeValues(yMin, yMax, 0, imgHeight - 1); + + for (int i = yMin; i < yMax; i++) { + for (int j = xMin; j < xMax; j++) { + // left to right, white-> black + if (!image_->get(j, i) && image_->get(j + 1, i)) { + Ref bottomLeftPoint(new ResultPoint(j, i)); + if (checkTolerance(topLeft, bottomLeft, bottomLeftRect, modelSize, + bottomLeftPoint, 1)) { + bottomLeftPoints.push_back(bottomLeftPoint); + break; + } + } + } + } + } else { + // (-45, -135) TopRight: bottom, white->black; BottomRight: left, black->white + xMin = topRightRect.x + modelSize; + xMax = topRightRect.x - modelSize + topRightRect.width; + yMin = topRightRect.y + topRightRect.height - 2 * modelSize; + yMax = topRightRect.y + topRightRect.height; + + MathUtils::getRangeValues(xMin, xMax, 0, imgWidth - 1); + MathUtils::getRangeValues(yMin, yMax, 0, imgHeight - 1); + + for (int j = xMin; j < xMax; j++) { + for (int i = yMin; i < yMax; i++) { + // top to down, white->balck + if (!image_->get(j, i) && image_->get(j, i + 1)) { + Ref topRightPoint(new ResultPoint(j, i)); + if (checkTolerance(topLeft, topRight, topRightRect, modelSize, + topRightPoint, 3)) { + topRightPoints.push_back(topRightPoint); + break; + } + } + } + } + + xMin = bottomLeftRect.x; + xMax = bottomLeftRect.x + 2 * modelSize; + yMin = bottomLeftRect.y + modelSize; + yMax = bottomLeftRect.y + bottomLeftRect.height - modelSize; + + MathUtils::getRangeValues(xMin, xMax, 0, imgWidth - 1); + MathUtils::getRangeValues(yMin, yMax, 0, imgHeight - 1); + + for (int i = yMin; i < yMax; i++) { + for (int j = xMin; j < xMax; j++) { + // left to right, black->white + if (image_->get(j, i) && !image_->get(j + 1, i)) { + Ref bottomLeftPoint(new ResultPoint(j, i)); + if (checkTolerance(topLeft, bottomLeft, bottomLeftRect, modelSize, + bottomLeftPoint, 0)) { + bottomLeftPoints.push_back(bottomLeftPoint); + break; + } + } + } + } + } + } +} + +Ref Detector::createTransform(Ref info, + Ref alignmentPattern, + int dimension) { + Ref topLeft(info->getTopLeft()); + Ref topRight(info->getTopRight()); + Ref bottomLeft(info->getBottomLeft()); + Ref transform = + createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension); + return transform; +} + +Ref Detector::createTransform(Ref topLeft, + Ref topRight, + Ref bottomLeft, + Ref alignmentPattern, + int dimension) { + float dimMinusThree = (float)dimension - 3.5f; + float bottomRightX; + float bottomRightY; + float sourceBottomRightX; + float sourceBottomRightY; + if (alignmentPattern && alignmentPattern->getX()) { + 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(); + float deltaX = topLeft->getX() - bottomLeft->getX(); + float deltaY = topLeft->getY() - bottomLeft->getY(); + if (fabs(deltaX) < fabs(deltaY)) + deltaY = topLeft->getY() - topRight->getY(); + else + deltaX = topLeft->getX() - topRight->getX(); + bottomRightX += 2 * deltaX; + bottomRightY += 2 * deltaY; + sourceBottomRightX = dimMinusThree; + sourceBottomRightY = dimMinusThree; + } + Ref transform(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())); + return 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. +int Detector::computeDimension(Ref topLeft, Ref topRight, + Ref bottomLeft, float moduleSizeX, float moduleSizeY) { + int tltrCentersDimension = ResultPoint::distance(topLeft, topRight) / moduleSizeX; + int tlblCentersDimension = ResultPoint::distance(topLeft, bottomLeft) / moduleSizeY; + + float tmp_dimension = ((tltrCentersDimension + tlblCentersDimension) / 2.0) + 7.0; + int dimension = cvRound(tmp_dimension); + int mod = dimension & 0x03; // mod 4 + + switch (mod) { // mod 4 + case 0: + dimension++; + break; + // 1? do nothing + case 2: + dimension--; + break; + } + return dimension; +} + +bool Detector::checkConvexQuadrilateral(Ref topLeft, Ref topRight, + Ref bottomLeft, Ref bottomRight) { + float v1[2]; + float v2[2]; + float v3[2]; + float v4[2]; + + v1[0] = topLeft->getX() - topRight->getX(); + v1[1] = topLeft->getY() - topRight->getY(); + v2[0] = topRight->getX() - bottomRight->getX(); + v2[1] = topRight->getY() - bottomRight->getY(); + v3[0] = bottomRight->getX() - bottomLeft->getX(); + v3[1] = bottomRight->getY() - bottomLeft->getY(); + v4[0] = bottomLeft->getX() - topLeft->getX(); + v4[1] = bottomLeft->getY() - topLeft->getY(); + + float c1 = MathUtils::VecCross(v1, v2); + float c2 = MathUtils::VecCross(v2, v3); + float c3 = MathUtils::VecCross(v3, v4); + float c4 = MathUtils::VecCross(v4, v1); + + if ((c1 < 0.0 && c2 < 0.0 && c3 < 0.0 && c4 < 0.0) || + (c1 > 0.0 && c2 > 0.0 && c3 > 0.0 && c4 > 0.0)) + return true; + else + return false; +} diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/detector.hpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/detector.hpp new file mode 100644 index 0000000..fbb8147 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/detector.hpp @@ -0,0 +1,145 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DETECTOR_DETECTOR_HPP_ +#define __ZXING_QRCODE_DETECTOR_DETECTOR_HPP_ + +#include "../../common/bitmatrix.hpp" +#include "../../common/counted.hpp" +#include "../../common/detector_result.hpp" +#include "../../common/perspective_transform.hpp" +#include "../../common/unicomblock.hpp" +#include "../../errorhandler.hpp" +#include "alignment_pattern.hpp" +#include "finder_pattern.hpp" +#include "finder_pattern_info.hpp" +#include "pattern_result.hpp" + +namespace zxing { +class DecodeHints; + +namespace qrcode { + +// Possible Detect Result + +class Detector : public Counted { +public: + enum DetectorState { + START = 10, + FINDFINDERPATTERN = 11, + FINDALIGNPATTERN = 12, + }; + + // Fix module size error when LEFT_SPILL or RIGHT_SPILL + enum FinderPatternMode { + NORMAL = 0, + LEFT_SPILL = 1, + RIGHT_SPILL = 2, + UP_SPILL = 3, + DOWN_SPILL = 4, + }; + + typedef struct Rect_ { + int x; + int y; + int width; + int height; + } Rect; + +private: + Ref image_; + Ref block_; + + vector > possiblePatternResults_; + + + DetectorState detectorState_; + +protected: + Ref getImage() const; + static int computeDimension(Ref topLeft, Ref topRight, + Ref bottomLeft, float moduleSizeX, float moduleSizeY); + float calculateModuleSize(Ref topLeft, Ref topRight, + Ref bottomLeft); + float calculateModuleSizeOneWay(Ref pattern, Ref otherPattern, + int patternState, int otherPatternState); + float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY, int patternState, + bool isReverse); + float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY); + float sizeOfBlackWhiteBlackRun(int fromX, int fromY, int toX, int toY); + Ref findAlignmentInRegion(float overallEstModuleSize, int estAlignmentX, + int estAlignmentY, float allowanceFactor, + ErrorHandler &err_handler); + Ref findAlignmentWithFitLine(Ref topLeft, + Ref topRight, + Ref bottomLeft, float moduleSize, + ErrorHandler &err_handler); + int fitLine(vector > &oldPoints, float &k, float &b, int &a); + bool checkTolerance(Ref &topLeft, Ref &topRight, Rect &topRightRect, + double modelSize, Ref &p, int flag); + void findPointsForLine(Ref &topLeft, Ref &topRight, + Ref &bottomLeft, Rect topRightRect, Rect bottomLeftRect, + vector > &topRightPoints, + vector > &bottomLeftPoints, float modelSize); + bool checkConvexQuadrilateral(Ref topLeft, Ref topRight, + Ref bottomLeft, Ref bottomRight); + +public: + virtual Ref createTransform(Ref topLeft, + Ref topRight, + Ref bottomLeft, + Ref alignmentPattern, + int dimension); + Ref createTransform(Ref finderPatternInfo, + Ref alignmentPattern, int dimension); + + static Ref sampleGrid(Ref image, int dimension, Ref, + ErrorHandler &err_handler); + + Detector(Ref image, Ref block); + void detect(DecodeHints const &hints, ErrorHandler &err_handler); + Ref getResultViaAlignment(int patternIdx, int alignmentIndex, + int possibleDimension, ErrorHandler &err_handler); + + int getPossiblePatternCount() { return possiblePatternResults_.size(); } + int getPossibleAlignmentCount(int idx); + + Ref getNearestAlignmentPattern(int tryFindRange, float moduleSize, + int estAlignmentX, int estAlignmentY); + bool hasSameResult(vector > possibleAlignmentPatterns, + Ref alignmentPattern); + void fixAlignmentPattern(float &alignmentX, float &alignmentY, float moduleSize); + + Ref processFinderPatternInfo(Ref info, + ErrorHandler &err_handler); + +public: + Ref getFinderPatternInfo(int idx) { + return possiblePatternResults_[idx]->finderPatternInfo; + } + Ref getAlignmentPattern(int patternIdx, int alignmentIdx) { + return possiblePatternResults_[patternIdx]->possibleAlignmentPatterns[alignmentIdx]; + } + + DetectorState getState() { return detectorState_; } + + unsigned int getPossibleVersion(int idx) { + return possiblePatternResults_[idx]->possibleVersion; + } + float getPossibleFix(int idx) { return possiblePatternResults_[idx]->possibleFix; } + float getPossibleModuleSize(int idx) { + return possiblePatternResults_[idx]->possibleModuleSize; + } + int getDimension(int idx) { return possiblePatternResults_[idx]->possibleDimension; } +}; +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DETECTOR_DETECTOR_HPP_ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern.cpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern.cpp new file mode 100644 index 0000000..c357f31 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern.cpp @@ -0,0 +1,91 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "finder_pattern.hpp" + +using zxing::Ref; + +namespace zxing { +namespace qrcode { + +FinderPattern::FinderPattern(float posX, float posY, float estimatedModuleSize) + : ResultPoint(posX, posY), + estimatedModuleSize_(estimatedModuleSize), + count_(1), + horizontalState_(FinderPattern::HORIZONTAL_STATE_NORMAL), + verticalState_(FinderPattern::VERTICAL_STATE_NORMAL) { + fix_ = -1.0f; +} + +FinderPattern::FinderPattern(float posX, float posY, float estimatedModuleSize, int count) + : ResultPoint(posX, posY), + estimatedModuleSize_(estimatedModuleSize), + count_(count), + horizontalState_(FinderPattern::HORIZONTAL_STATE_NORMAL), + verticalState_(FinderPattern::VERTICAL_STATE_NORMAL) { + fix_ = -1.0f; +} +int FinderPattern::getCount() const { return count_; } +void FinderPattern::incrementCount() { count_++; } + +bool FinderPattern::aboutEquals(float moduleSize, float i, float j) const { + if (abs(i - getY()) <= moduleSize && abs(j - getX()) <= moduleSize) { + float moduleSizeDiff = abs(moduleSize - estimatedModuleSize_); + return moduleSizeDiff <= 1.0f || moduleSizeDiff <= estimatedModuleSize_; + } + return false; +} + +float FinderPattern::getEstimatedModuleSize() const { return estimatedModuleSize_; } + +Ref FinderPattern::combineEstimate(float i, float j, float newModuleSize) const { + int combinedCount = count_ + 1; + float combinedX = getX(); + float combinedY = getY(); + float combinedModuleSize = getEstimatedModuleSize(); + if (combinedCount <= 3) { + combinedX = (count_ * getX() + j) / combinedCount; + combinedY = (count_ * getY() + i) / combinedCount; + combinedModuleSize = (count_ * getEstimatedModuleSize() + newModuleSize) / combinedCount; + } + return Ref( + new FinderPattern(combinedX, combinedY, combinedModuleSize, combinedCount)); +} + +void FinderPattern::setHorizontalCheckState(int state) { + switch (state) { + case 0: + horizontalState_ = FinderPattern::HORIZONTAL_STATE_NORMAL; + break; + case 1: + horizontalState_ = FinderPattern::HORIZONTAL_STATE_LEFT_SPILL; + break; + case 2: + horizontalState_ = FinderPattern::HORIZONTAL_STATE_RIGHT_SPILL; + break; + } + return; +} +void FinderPattern::setVerticalCheckState(int state) { + switch (state) { + case 0: + verticalState_ = FinderPattern::VERTICAL_STATE_NORMAL; + break; + case 1: + verticalState_ = FinderPattern::VERTICAL_STATE_UP_SPILL; + break; + case 2: + verticalState_ = FinderPattern::VERTICAL_STATE_DOWN_SPILL; + break; + } + return; +} +} // namespace qrcode +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern.hpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern.hpp new file mode 100644 index 0000000..d7733e0 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern.hpp @@ -0,0 +1,59 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DETECTOR_FINDER_PATTERN_HPP_ +#define __ZXING_QRCODE_DETECTOR_FINDER_PATTERN_HPP_ + +#include "../../common/bitmatrix.hpp" +#include "../../resultpoint.hpp" + +namespace zxing { +namespace qrcode { + +class FinderPattern : public ResultPoint { +public: + enum CheckState { + HORIZONTAL_STATE_NORMAL = 0, + HORIZONTAL_STATE_LEFT_SPILL = 1, + HORIZONTAL_STATE_RIGHT_SPILL = 2, + VERTICAL_STATE_NORMAL = 3, + VERTICAL_STATE_UP_SPILL = 4, + VERTICAL_STATE_DOWN_SPILL = 5 + }; + +private: + float estimatedModuleSize_; + int count_; + + FinderPattern(float posX, float posY, float estimatedModuleSize, int count); + +public: + FinderPattern(float posX, float posY, float estimatedModuleSize); + int getCount() const; + float getEstimatedModuleSize() const; + void incrementCount(); + bool aboutEquals(float moduleSize, float i, float j) const; + Ref combineEstimate(float i, float j, float newModuleSize) const; + + void setHorizontalCheckState(int state); + void setVerticalCheckState(int state); + + int getHorizontalCheckState() { return horizontalState_; } + int getVerticalCheckState() { return verticalState_; } + +private: + float fix_; + CheckState horizontalState_; + CheckState verticalState_; +}; +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DETECTOR_FINDER_PATTERN_HPP_ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_finder.cpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_finder.cpp new file mode 100644 index 0000000..438928c --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_finder.cpp @@ -0,0 +1,1508 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "finder_pattern_finder.hpp" +#include "../../common/kmeans.hpp" +#include "../../common/mathutils.hpp" +#include "../../decodehints.hpp" +#include "../../errorhandler.hpp" + +using zxing::Ref; +using zxing::qrcode::FinderPattern; +using zxing::qrcode::FinderPatternFinder; +using zxing::qrcode::FinderPatternInfo; + +// VC++ + +using zxing::BitMatrix; +using zxing::DecodeHints; +using zxing::ResultPoint; + +namespace zxing { + +namespace qrcode { + + +namespace { +class FurthestFromAverageComparator { +private: + const float averageModuleSize_; + +public: + explicit FurthestFromAverageComparator(float averageModuleSize) + : averageModuleSize_(averageModuleSize) {} + bool operator()(Ref a, Ref b) { + float dA = abs(a->getEstimatedModuleSize() - averageModuleSize_); + float dB = abs(b->getEstimatedModuleSize() - averageModuleSize_); + return dA > dB; + } +}; + +// Orders by furthes from average +class CenterComparator { + const float averageModuleSize_; + +public: + explicit CenterComparator(float averageModuleSize) : averageModuleSize_(averageModuleSize) {} + bool operator()(Ref a, Ref b) { + // N.B.: we want the result in descending order ... + if (a->getCount() != b->getCount()) { + return a->getCount() > b->getCount(); + } else { + float dA = abs(a->getEstimatedModuleSize() - averageModuleSize_); + float dB = abs(b->getEstimatedModuleSize() - averageModuleSize_); + return dA < dB; + } + } +}; + +class CountComparator { +public: + bool operator()(Ref a, Ref b) { + return a->getCount() > b->getCount(); + } +}; + +class ModuleSizeComparator { +public: + bool operator()(Ref a, Ref b) { + return a->getEstimatedModuleSize() > b->getEstimatedModuleSize(); + } +}; + +class BestComparator { +public: + bool operator()(Ref a, Ref b) { + if (a->getCount() != b->getCount()) { + return a->getCount() > b->getCount(); + } else { + return a->getEstimatedModuleSize() > b->getEstimatedModuleSize(); + } + } +}; +class BestComparator2 { +public: + bool operator()(Ref a, Ref b) { + if (a->getCount() != b->getCount()) { + return a->getCount() > b->getCount(); + } else { + int aErr = 0, bErr = 0; + if (a->getHorizontalCheckState() != FinderPattern::HORIZONTAL_STATE_NORMAL) aErr++; + if (a->getVerticalCheckState() != FinderPattern::VERTICAL_STATE_NORMAL) aErr++; + if (b->getHorizontalCheckState() != FinderPattern::HORIZONTAL_STATE_NORMAL) bErr++; + if (b->getVerticalCheckState() != FinderPattern::VERTICAL_STATE_NORMAL) bErr++; + + if (aErr != bErr) { + return aErr < bErr; + } else { + return a->getEstimatedModuleSize() > b->getEstimatedModuleSize(); + } + } + } +}; + +class XComparator { +public: + bool operator()(Ref a, Ref b) { return a->getX() < b->getX(); } +}; + +class YComparator { +public: + bool operator()(Ref a, Ref b) { return a->getY() < b->getY(); } +}; + +} // namespace + +int FinderPatternFinder::CENTER_QUORUM = 2; +int FinderPatternFinder::MIN_SKIP = 1; // 1 pixel/module times MIN_SKIP modules/center +int FinderPatternFinder::MAX_MODULES = 177; // support up to version 40 which has 177 modules +int FinderPatternFinder::INTEGER_MATH_SHIFT = 8; +int FinderPatternFinder::FP_INPUT_CNN_MAX_NUM = 10; +int FinderPatternFinder::FP_IS_SELECT_BEST = 1; +int FinderPatternFinder::FP_IS_SELECT_FILE_BEST = 1; +int FinderPatternFinder::FP_INPUT_MAX_NUM = 100; +int FinderPatternFinder::FP_FILTER_SIZE = 100; +int FinderPatternFinder::FPS_CLUSTER_MAX = 4; +int FinderPatternFinder::FPS_RESULT_MAX = 12; +int FinderPatternFinder::K_FACTOR = 2; + +float FinderPatternFinder::FPS_MS_VAL = 1.0f; +float FinderPatternFinder::FP_COUNT_MIN = 2.0f; +float FinderPatternFinder::FP_MS_MIN = 1.0f; +float FinderPatternFinder::FP_RIGHT_ANGLE = 0.342f; +float FinderPatternFinder::FP_SMALL_ANGLE1 = 0.8191f; +float FinderPatternFinder::FP_SMALL_ANGLE2 = 0.5736f; +float FinderPatternFinder::QR_MIN_FP_AREA_ERR = 3; +float FinderPatternFinder::QR_MIN_FP_MS_ERR = 1; +int FinderPatternFinder::QR_MIN_FP_ACCEPT = 4; + +std::vector> FinderPatternFinder::find(DecodeHints const& hints, + ErrorHandler& err_handler) { + bool tryHarder = true; + + size_t maxI = image_->getHeight(); + size_t maxJ = image_->getWidth(); + // Init pre check result + _horizontalCheckedResult.clear(); + _horizontalCheckedResult.resize(maxJ); + // As this is used often, we use an integer array instead of vector + int stateCount[5]; + + // Let's assume that the maximum version QR Code we support + // (Version 40, 177modules, and finder pattern start at: 0~7) 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; + } + + // This is slightly faster than using the Ref. Efficiency is important here + BitMatrix& matrix = *image_; + + // If we need to use getRowRecords or getRowCounterOffsetEnd, we should call + // initRowCounters first + matrix.initRowCounters(); + + // scan line algorithm + for (size_t i = iSkip - 1; i < maxI; i += iSkip) { + COUNTER_TYPE* irow_states = matrix.getRowRecords(i); + COUNTER_TYPE* irow_offsets = matrix.getRowRecordsOffset(i); + + size_t rj = matrix.getRowFirstIsWhite(i) ? 1 : 0; + COUNTER_TYPE row_counter_width = matrix.getRowCounterOffsetEnd(i); + // because the rj is black, rj+1 must be white, so we can skip it by +2 + for (; (rj + 4) < size_t(row_counter_width) && (rj + 4) < maxJ; rj += 2) { + stateCount[0] = irow_states[rj]; + stateCount[1] = irow_states[rj + 1]; + stateCount[2] = irow_states[rj + 2]; + stateCount[3] = irow_states[rj + 3]; + stateCount[4] = irow_states[rj + 4]; + + size_t j = irow_offsets[rj + 4] + stateCount[4]; + if (j > maxJ) { + rj = row_counter_width - 1; + continue; + } + if (foundPatternCross(stateCount)) { + if (j == maxJ) { + // check whether it is the "true" central + bool confirmed = handlePossibleCenter(stateCount, i, maxJ); + if (confirmed) { + iSkip = int(possibleCenters_.back()->getEstimatedModuleSize()); + if (iSkip < 1) iSkip = 1; + } + rj = row_counter_width - 1; + continue; + } else { + bool 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_) { + 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; + rj = row_counter_width - 1; + j = maxJ - 1; + } + } + } else { + continue; + } + rj += 4; + } + } + } + } + // use connected cells algorithm + { + for (size_t i = iSkip - 1; i < maxI; i += iSkip) { + COUNTER_TYPE* irow_states = matrix.getRowRecords(i); + COUNTER_TYPE* irow_offsets = matrix.getRowRecordsOffset(i); + COUNTER_TYPE row_counter_width = matrix.getRowCounterOffsetEnd(i); + + for (size_t rj = matrix.getRowFirstIsWhite(i) ? 1 : 0; + (rj + 4) < size_t(row_counter_width); rj += 2) { + if (block_->GetUnicomBlockIndex(i, irow_offsets[rj]) == + block_->GetUnicomBlockIndex(i, irow_offsets[rj + 4]) && + block_->GetUnicomBlockIndex(i, irow_offsets[rj + 1]) == + block_->GetUnicomBlockIndex(i, irow_offsets[rj + 3]) && + block_->GetUnicomBlockIndex(i, irow_offsets[rj]) != + block_->GetUnicomBlockIndex(i, irow_offsets[rj + 2])) { + const int iBlackCir = block_->GetUnicomBlockSize(i, irow_offsets[rj]); + const int iWhiteCir = block_->GetUnicomBlockSize(i, irow_offsets[rj + 1]); + const int iBlackPnt = block_->GetUnicomBlockSize(i, irow_offsets[rj + 2]); + + if (-1 == iBlackCir || -1 == iWhiteCir) continue; + + const float fBlackCir = sqrt(iBlackCir / 24.0); + const float fWhiteCir = sqrt(iWhiteCir / 16.0); + const float fBlackPnt = sqrt(iBlackPnt / 9.0); + + // use center 1:3:1, because the border may be padded. + // a plan for MS + const float fRealMS = sqrt((iWhiteCir + iBlackPnt) / 25.0); + + // b plan for MS + int iTotalCount = 0; + for (int j = 1; j < 4; ++j) iTotalCount += irow_states[rj + j]; + const float fEstRowMS = iTotalCount / 5.0; + + if (fabs(fBlackCir - fWhiteCir) <= QR_MIN_FP_AREA_ERR && + fabs(fWhiteCir - fBlackPnt) <= QR_MIN_FP_AREA_ERR && + fabs(fRealMS - fEstRowMS) < QR_MIN_FP_MS_ERR) { + int centerI = 0; + int centerJ = 0; + if (fRealMS < QR_MIN_FP_ACCEPT) { + centerI = i; + centerJ = irow_offsets[rj + 2] + irow_states[rj + 2] / 2; + } else { + int iMinX = 0, iMinY = 0, iMaxX = 0, iMaxY = 0; + block_->GetMinPoint(i, irow_offsets[rj + 1], iMinY, iMinX); + block_->GetMaxPoint(i, irow_offsets[rj + 3], iMaxY, iMaxX); + centerI = (iMaxY + iMinY) / 2.0; // y + centerJ = (iMaxX + iMinX) / 2.0; // x + } + tryToPushToCenters(centerI, centerJ, fRealMS); + int rowSkip = findRowSkip(); + if (rowSkip > irow_states[rj + 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 - irow_states[rj + 2] - iSkip; + rj = row_counter_width - 1; + } + rj += 4; + } + } + } + } + } + + vector> patternInfos = getPatternInfosFileMode(hints, err_handler); + if (err_handler.ErrCode()) { + return std::vector>(); + } + // sort with score + sort(patternInfos.begin(), patternInfos.end(), + [](Ref a, Ref b) { + return a->getPossibleFix() > b->getPossibleFix(); + }); + + return patternInfos; +} + +bool FinderPatternFinder::tryToPushToCenters(float centerI, float centerJ, + float estimatedModuleSize, + CrossCheckState horizontalState, + CrossCheckState verticalState) { + for (size_t index = 0; index < possibleCenters_.size(); index++) { + Ref center = possibleCenters_[index]; + // Look for about the same center and module size: + if (center->aboutEquals(estimatedModuleSize, centerI, centerJ)) { + possibleCenters_[index] = + center->combineEstimate(centerI, centerJ, estimatedModuleSize); + possibleCenters_[index]->setHorizontalCheckState( + horizontalState == FinderPatternFinder::NORMAL ? center->getHorizontalCheckState() + : horizontalState); + possibleCenters_[index]->setVerticalCheckState( + verticalState == FinderPatternFinder::NORMAL ? center->getVerticalCheckState() + : verticalState); + return false; + } + } + Ref newPattern(new FinderPattern(centerJ, centerI, estimatedModuleSize)); + newPattern->setHorizontalCheckState(horizontalState); + newPattern->setVerticalCheckState(verticalState); + possibleCenters_.push_back(newPattern); + return true; +} + +bool FinderPatternFinder::isEqualResult(Ref src, Ref dst) { + if (src == NULL) { + return false; + } + + if (dst == NULL) { + return true; + } + + auto topLeft = src->getTopLeft(); + auto bottomLeft = src->getBottomLeft(); + auto topRight = src->getTopRight(); + + return topLeft->aboutEquals(1.0, dst->getTopLeft()->getY(), dst->getTopLeft()->getX()) && + bottomLeft->aboutEquals(1.0, dst->getBottomLeft()->getY(), + dst->getBottomLeft()->getX()) && + topRight->aboutEquals(1.0, dst->getTopRight()->getY(), dst->getTopRight()->getX()); +} + +bool FinderPatternFinder::IsPossibleFindPatterInfo(Ref a, Ref b, + Ref c) { + // check variance + float aMs = a->getEstimatedModuleSize(); + float bMs = b->getEstimatedModuleSize(); + float cMs = c->getEstimatedModuleSize(); + + float avg = (aMs + bMs + cMs) / 3.0; + float val = + sqrt((aMs - avg) * (aMs - avg) + (bMs - avg) * (bMs - avg) + (cMs - avg) * (cMs - avg)); + + if (val >= FPS_MS_VAL) return false; + + float longSize = 0.0; + + return checkIsoscelesRightTriangle(a, b, c, longSize); +} + +void FinderPatternFinder::PushToResult(Ref a, Ref b, + Ref c, + vector>& patternInfos) { + vector> finderPatterns; + finderPatterns.push_back(a); + finderPatterns.push_back(b); + finderPatterns.push_back(c); + vector> finderPattern = orderBestPatterns(finderPatterns); + + Ref patternInfo(new FinderPatternInfo(finderPattern)); + + for (size_t j = 0; j < patternInfos.size(); j++) { + if (isEqualResult(patternInfos[j], patternInfo)) { + return; + } + } + patternInfos.push_back(patternInfo); +} + +vector> FinderPatternFinder::getPatternInfosFileMode( + DecodeHints const& hints, ErrorHandler& err_handler) { + size_t startSize = possibleCenters_.size(); + + if (startSize < 3) { + // Couldn't find enough finder patterns + err_handler = ReaderErrorHandler("Could not find three finder patterns"); + return vector>(); + } + + std::vector> patternInfos; + + if (startSize == 3) { + PushToResult(possibleCenters_[0], possibleCenters_[1], possibleCenters_[2], patternInfos); + return patternInfos; + } + + vector> finderPatterns; + Ref resultBest; + + // select best + if (FP_IS_SELECT_BEST) { + finderPatterns = selectBestPatterns(err_handler); + if (err_handler.ErrCode() == 0) + PushToResult(finderPatterns[0], finderPatterns[1], finderPatterns[2], patternInfos); + } + + if (FP_IS_SELECT_FILE_BEST) { + finderPatterns = selectFileBestPatterns(err_handler); + if (err_handler.ErrCode() == 0) + PushToResult(finderPatterns[0], finderPatterns[1], finderPatterns[2], patternInfos); + } + + // sort and filter + sort(possibleCenters_.begin(), possibleCenters_.end(), ModuleSizeComparator()); + std::vector> standardCenters; + + for (size_t i = 0; i < possibleCenters_.size(); i++) { + if (possibleCenters_[i]->getEstimatedModuleSize() >= FP_MS_MIN && + possibleCenters_[i]->getCount() >= FP_COUNT_MIN) { + standardCenters.push_back(possibleCenters_[i]); + if (standardCenters.size() >= size_t(FP_INPUT_MAX_NUM)) break; + if (hints.getUseNNDetector() && standardCenters.size() >= size_t(FP_INPUT_CNN_MAX_NUM)) + break; + } + } + + if (standardCenters.size() < 3) { + err_handler = ReaderErrorHandler("Could not find three finder patterns"); + return vector>(); + } + + if (standardCenters.size() <= size_t(FP_INPUT_CNN_MAX_NUM)) { + for (size_t x = 0; x < standardCenters.size(); x++) { + for (size_t y = x + 1; y < standardCenters.size(); y++) { + for (size_t z = y + 1; z < standardCenters.size(); z++) { + bool check_result = IsPossibleFindPatterInfo( + standardCenters[x], standardCenters[y], standardCenters[z]); + if (check_result) { + PushToResult(standardCenters[x], standardCenters[y], standardCenters[z], + patternInfos); + } + } + } + } + return patternInfos; + } + + // Kmeans + const int maxepoches = 100; + const int minchanged = 0; + // calculate K + int k = log(float(standardCenters.size())) * K_FACTOR - 1; + if (k < 1) k = 1; + + vector> trainX; + for (size_t i = 0; i < standardCenters.size(); i++) { + vector tmp; + tmp.push_back(standardCenters[i]->getCount()); + tmp.push_back(standardCenters[i]->getEstimatedModuleSize()); + trainX.push_back(tmp); + } + + vector clusters_out = k_means(trainX, k, maxepoches, minchanged); + + for (size_t i = 0; i < clusters_out.size(); i++) { + int cluster_select = 0; + + if (clusters_out[i].samples.size() < 3) { + if (i < clusters_out.size() - 1 && clusters_out[i + 1].samples.size() < 3) { + for (size_t j = 0; j < clusters_out[i].samples.size(); j++) + clusters_out[i + 1].samples.push_back(clusters_out[i].samples[j]); + } + continue; + } + + vector> clusterPatterns; + for (size_t j = 0; j < clusters_out[i].samples.size(); j++) { + clusterPatterns.push_back(standardCenters[clusters_out[i].samples[j]]); + } + + sort(clusterPatterns.begin(), clusterPatterns.end(), BestComparator2()); + + for (size_t x = 0; + x < clusters_out[i].samples.size() && cluster_select <= FPS_CLUSTER_MAX && + patternInfos.size() <= size_t(FPS_RESULT_MAX); + x++) { + for (size_t y = x + 1; + y < clusters_out[i].samples.size() && cluster_select <= FPS_CLUSTER_MAX && + patternInfos.size() <= size_t(FPS_RESULT_MAX); + y++) { + for (size_t z = y + 1; + z < clusters_out[i].samples.size() && cluster_select <= FPS_CLUSTER_MAX && + patternInfos.size() <= size_t(FPS_RESULT_MAX); + z++) { + bool check_result = IsPossibleFindPatterInfo( + clusterPatterns[x], clusterPatterns[y], clusterPatterns[z]); + if (check_result) { + PushToResult(clusterPatterns[x], clusterPatterns[y], clusterPatterns[z], + patternInfos); + cluster_select++; + } + } + } + } + } + return patternInfos; +} + +// 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. +float FinderPatternFinder::centerFromEnd(int* stateCount, int end) { + // calculate the center by pattern 1:3:1 is better than pattern 3 + // because the finder pattern is irregular in some case + return (float)(end - stateCount[4]) - (stateCount[3] + stateCount[2] + stateCount[1]) / 2.0f; +} + +// return if the proportions of the counts is close enough to 1/1/3/1/1 ratios +// used by finder patterns to be considered a match +bool FinderPatternFinder::foundPatternCross(int* stateCount) { + int totalModuleSize = 0; + + int stateCountINT[5]; + + int minModuleSizeINT = 3; + minModuleSizeINT <<= INTEGER_MATH_SHIFT; + + for (int i = 0; i < 5; i++) { + if (stateCount[i] <= 0) { + return false; + } + stateCountINT[i] = stateCount[i] << INTEGER_MATH_SHIFT; + totalModuleSize += stateCount[i]; + } + if (totalModuleSize < 7) { + return false; + } + + CURRENT_CHECK_STATE = FinderPatternFinder::NOT_PATTERN; + + totalModuleSize = totalModuleSize << INTEGER_MATH_SHIFT; + + // Newer version to check 1 time, use 3 points + int moduleSize = ((totalModuleSize - stateCountINT[0] - stateCountINT[4])) / 5; + + int maxVariance = moduleSize; + + if (moduleSize > minModuleSizeINT) maxVariance = moduleSize / 2; + + int startCountINT = stateCountINT[0]; + int endCountINT = stateCountINT[4]; + + bool leftFit = (abs(moduleSize - startCountINT) <= maxVariance); + bool rightFit = (abs(moduleSize - endCountINT) <= maxVariance); + + if (leftFit) { + if (rightFit) { + moduleSize = totalModuleSize / 7; + CURRENT_CHECK_STATE = FinderPatternFinder::NORMAL; + } else { + moduleSize = (totalModuleSize - stateCountINT[4]) / 6; + CURRENT_CHECK_STATE = FinderPatternFinder::RIHGT_SPILL; + } + } else { + if (rightFit) { + moduleSize = (totalModuleSize - stateCountINT[0]) / 6; + CURRENT_CHECK_STATE = FinderPatternFinder::LEFT_SPILL; + } else { + // return false; + CURRENT_CHECK_STATE = FinderPatternFinder::LEFT_RIGHT_SPILL; + } + } + + // 1:1:3:1:1 || n:1:3:1:1 || 1:1:3:1:n + if (abs(moduleSize - stateCountINT[1]) <= maxVariance && + abs(3 * moduleSize - stateCountINT[2]) <= 3 * maxVariance && + abs(moduleSize - stateCountINT[3]) <= maxVariance) { + return true; + } + return false; +} + +int FinderPatternFinder::getMinModuleSize() { + int minModuleSize = (3 * min(image_->getWidth(), image_->getHeight())) / (4 * MAX_MODULES); + + if (minModuleSize < MIN_SKIP) { + minModuleSize = MIN_SKIP; + } + + return minModuleSize; +} + +/** + * 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 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 + * @param originalStateCountTotal The original state count total. + * @return true if proportions are withing expected limits + */ +bool FinderPatternFinder::crossCheckDiagonal(int startI, int centerJ, int maxCount, + int originalStateCountTotal) { + int maxI = image_->getHeight(); + int maxJ = image_->getWidth(); + + if ((startI < 0) || (startI > maxI - 1) || (centerJ < 0) || (centerJ > maxJ - 1)) { + return false; + } + + int stateCount[5]; + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + stateCount[3] = 0; + stateCount[4] = 0; + + if (!image_->get(centerJ, startI)) { + if (startI + 1 < maxI && image_->get(centerJ, startI + 1)) + startI = startI + 1; + else if (0 < startI - 1 && image_->get(centerJ, startI - 1)) + startI = startI - 1; + else + return false; + } + + // This is slightly faster than using the Ref. Efficiency is important here + BitMatrix& matrix = *image_; + + // Start counting up, left from center finding black center mass + int i = 0; + // Fix possible crash 20140418 + // while (startI - i >= 0 && image.get(centerJ - i, startI - i)) { + while ((startI - i >= 0) && (centerJ - i >= 0) && matrix.get(centerJ - i, startI - i)) { + stateCount[2]++; + i++; + } + + if ((startI - i < 0) || (centerJ - i < 0)) { + return false; + } + + // Continue up, left finding white space + while ((startI - i >= 0) && (centerJ - i >= 0) && !matrix.get(centerJ - i, startI - i) && + stateCount[1] <= maxCount) { + stateCount[1]++; + i++; + } + + // If already too many modules in this state or ran off the edge: + if ((startI - i < 0) || (centerJ - i < 0) || stateCount[1] > maxCount) { + return false; + } + + CrossCheckState tmpCheckState = FinderPatternFinder::NORMAL; + + // Continue up, left finding black border + while ((startI - i >= 0) && (centerJ - i >= 0) && matrix.get(centerJ - i, startI - i) && + stateCount[0] <= maxCount) { + stateCount[0]++; + i++; + } + + if (stateCount[0] >= maxCount) { + tmpCheckState = FinderPatternFinder::LEFT_SPILL; + } + + // Now also count down, right from center + i = 1; + while ((startI + i < maxI) && (centerJ + i < maxJ) && matrix.get(centerJ + i, startI + i)) { + stateCount[2]++; + i++; + } + + // Ran off the edge? + if ((startI + i >= maxI) || (centerJ + i >= maxJ)) { + return false; + } + + while ((startI + i < maxI) && (centerJ + i < maxJ) && !matrix.get(centerJ + i, startI + i) && + stateCount[3] < maxCount) { + stateCount[3]++; + i++; + } + + if ((startI + i >= maxI) || (centerJ + i >= maxJ) || stateCount[3] >= maxCount) { + return false; + } + + while ((startI + i < maxI) && (centerJ + i < maxJ) && matrix.get(centerJ + i, startI + i) && + stateCount[4] < maxCount) { + stateCount[4]++; + i++; + } + + if (stateCount[4] >= maxCount) { + tmpCheckState = tmpCheckState == FinderPatternFinder::LEFT_SPILL + ? FinderPatternFinder::LEFT_RIGHT_SPILL + : FinderPatternFinder::RIHGT_SPILL; + } + + bool diagonal_check = foundPatternCross(stateCount); + if (!diagonal_check) return false; + + if (CURRENT_CHECK_STATE == FinderPatternFinder::LEFT_SPILL && + tmpCheckState == FinderPatternFinder::RIHGT_SPILL) + return false; + + if (CURRENT_CHECK_STATE == FinderPatternFinder::RIHGT_SPILL && + tmpCheckState == FinderPatternFinder::LEFT_SPILL) + return false; + + int stateCountTotal = getStateCountTotal(stateCount, CURRENT_CHECK_STATE); + + if (abs(stateCountTotal - originalStateCountTotal) < 2 * originalStateCountTotal) { + return true; + } else { + return false; + } +} + +int FinderPatternFinder::getStateCountTotal(int* stateCount, const CrossCheckState& check_state) { + int stateCountTotal = stateCount[1] + stateCount[2] + stateCount[3]; + if (check_state == FinderPatternFinder::NORMAL) { + stateCountTotal = stateCountTotal + stateCount[0] + stateCount[4]; + } else if (check_state == FinderPatternFinder::LEFT_SPILL) { + stateCountTotal = stateCountTotal + stateCount[1] + stateCount[4]; + } else if (check_state == FinderPatternFinder::RIHGT_SPILL) { + stateCountTotal = stateCountTotal + stateCount[0] + stateCount[3]; + } else if (check_state == FinderPatternFinder::LEFT_RIGHT_SPILL) { + stateCountTotal = stateCountTotal + stateCount[1] + stateCount[3]; + } + return stateCountTotal; +} +// 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. +float FinderPatternFinder::crossCheckVertical(size_t startI, size_t centerJ, int maxCount, + int originalStateCountTotal, + float& estimatedVerticalModuleSize) { + int maxI = image_->getHeight(); + + int stateCount[5]; + for (int i = 0; i < 5; i++) stateCount[i] = 0; + + if (!image_->get(centerJ, startI)) { + if ((int)startI + 1 < maxI && image_->get(centerJ, startI + 1)) + startI = startI + 1; + else if (0 < (int)startI - 1 && image_->get(centerJ, startI - 1)) + startI = startI - 1; + else + return nan(); + } + + // This is slightly faster than using the Ref. Efficiency is important here + BitMatrix& matrix = *image_; + + bool* imageRow0 = matrix.getRowBoolPtr(0); + bool* p = imageRow0; + int imgWidth = matrix.getWidth(); + + // Start counting up from center + int ii = startI; + + p = imageRow0 + ii * imgWidth + centerJ; + + while (ii >= 0 && *p) { + stateCount[2]++; + ii--; + p -= imgWidth; + } + if (ii < 0) { + return nan(); + } + while (ii >= 0 && !*p && stateCount[1] <= maxCount) { + stateCount[1]++; + ii--; + p -= imgWidth; + } + // If already too many modules in this state or ran off the edge: + if (ii < 0 || stateCount[1] > maxCount) { + return nan(); + } + + CrossCheckState tmpCheckState = FinderPatternFinder::NORMAL; + + while (ii >= 0 && *p /*&& stateCount[0] <= maxCount*/) { // n:1:3:1:1 + stateCount[0]++; + ii--; + p -= imgWidth; + } + + if (stateCount[0] >= maxCount) { + tmpCheckState = FinderPatternFinder::LEFT_SPILL; + } + + // Now also count down from center + ii = startI + 1; + + p = imageRow0 + ii * imgWidth + centerJ; + + while (ii < maxI && *p) { // 1:1:"3":1:1 + // while (ii < maxI && matrix.get(centerJ, ii)) { + stateCount[2]++; + ii++; + + p += imgWidth; + } + if (ii == maxI) { + return nan(); + } + while (ii < maxI && !*p && stateCount[3] < maxCount) { // 1:1:3:"1":1 + stateCount[3]++; + ii++; + + p += imgWidth; + } + if (ii == maxI || stateCount[3] >= maxCount) { + return nan(); + } + + if (tmpCheckState == FinderPatternFinder::LEFT_SPILL) { + while (ii < maxI && *p && stateCount[4] < maxCount) { // 1:1:3:1:"1" + stateCount[4]++; + ii++; + + p += imgWidth; + } + if (stateCount[4] >= maxCount) { + return nan(); + } + } else { // 1:1:3:1:"n" + while (ii < maxI && *p) { + stateCount[4]++; + ii++; + + p += imgWidth; + } + if (stateCount[4] >= maxCount) { + tmpCheckState = FinderPatternFinder::RIHGT_SPILL; + } + } + + bool vertical_check = foundPatternCross(stateCount); + if (!vertical_check) return nan(); + + if ((CURRENT_CHECK_STATE == FinderPatternFinder::LEFT_SPILL && + tmpCheckState == FinderPatternFinder::RIHGT_SPILL) || + (CURRENT_CHECK_STATE == FinderPatternFinder::RIHGT_SPILL && + tmpCheckState == FinderPatternFinder::LEFT_SPILL)) { + return nan(); + } + + int stateCountTotal = getStateCountTotal(stateCount, CURRENT_CHECK_STATE); + + // 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 + if (5 * abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { + return nan(); + } + + estimatedVerticalModuleSize = (float)stateCountTotal / 7.0f; + + return centerFromEnd(stateCount, ii); +} + +// Like #crossCheckVertical(), 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. +float FinderPatternFinder::crossCheckHorizontal(size_t startJ, size_t centerI, int maxCount, + int originalStateCountTotal, + float& estimatedHorizontalModuleSize) { + int maxJ = image_->getWidth(); + + int stateCount[5]; + for (int i = 0; i < 5; i++) stateCount[i] = 0; + + if (!image_->get(startJ, centerI)) { + if ((int)startJ + 1 < maxJ && image_->get(startJ + 1, centerI)) + startJ = startJ + 1; + else if (0 < (int)startJ - 1 && image_->get(startJ - 1, centerI)) + startJ = startJ - 1; + else + return nan(); + } + + // This is slightly faster than using the Ref. Efficiency is important here + BitMatrix& matrix = *image_; + int j = startJ; + + bool* centerIrow = NULL; + + centerIrow = matrix.getRowBoolPtr(centerI); + + // while (j >= 0 &&matrix.get(j, centerI)) { + while (j >= 0 && centerIrow[j]) { + stateCount[2]++; + j--; + } + if (j < 0) { + return nan(); + } + while (j >= 0 && !centerIrow[j] && stateCount[1] <= maxCount) { + stateCount[1]++; + j--; + } + if (j < 0 || stateCount[1] > maxCount) { + return nan(); + } + CrossCheckState tmpCheckState = FinderPatternFinder::NORMAL; + + while (j >= 0 && centerIrow[j] /* && stateCount[0] <= maxCount*/) { + stateCount[0]++; + j--; + } + if (stateCount[0] >= maxCount) { + tmpCheckState = FinderPatternFinder::LEFT_SPILL; + } + + j = startJ + 1; + while (j < maxJ && centerIrow[j]) { + stateCount[2]++; + j++; + } + if (j == maxJ) { + return nan(); + } + while (j < maxJ && !centerIrow[j] && stateCount[3] < maxCount) { + stateCount[3]++; + j++; + } + if (j == maxJ || stateCount[3] >= maxCount) { + return nan(); + } + + if (tmpCheckState == LEFT_SPILL) { + while (j < maxJ && centerIrow[j] && stateCount[4] <= maxCount) { + stateCount[4]++; + j++; + } + if (stateCount[4] >= maxCount) { + return nan(); + } + } else { + while (j < maxJ && centerIrow[j]) { + stateCount[4]++; + j++; + } + if (stateCount[4] >= maxCount) { + tmpCheckState = RIHGT_SPILL; + } + } + + while (j < maxJ && centerIrow[j] /*&& stateCount[4] < maxCount*/) { + stateCount[4]++; + j++; + } + + // 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]; + bool horizontal_check = foundPatternCross(stateCount); + if (!horizontal_check) return nan(); + + /* + if(tmpCheckState!=CURRENT_CHECK_STATE) + return nan();*/ + + // Cannot be a LEFT-RIGHT center + if ((CURRENT_CHECK_STATE == FinderPatternFinder::LEFT_SPILL && + tmpCheckState == FinderPatternFinder::RIHGT_SPILL) || + (CURRENT_CHECK_STATE == FinderPatternFinder::RIHGT_SPILL && + tmpCheckState == FinderPatternFinder::LEFT_SPILL)) { + return nan(); + } + + int stateCountTotal = getStateCountTotal(stateCount, CURRENT_CHECK_STATE); + if (5 * abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { + return nan(); + } + + estimatedHorizontalModuleSize = (float)stateCountTotal / 7.0f; + return centerFromEnd(stateCount, j); +} + +float FinderPatternFinder::hasHorizontalCheckedResult(size_t startJ, size_t centerI) { + for (size_t i = 0; i < _horizontalCheckedResult[startJ].size(); i++) { + if (_horizontalCheckedResult[startJ][i].centerI == centerI) { + return _horizontalCheckedResult[startJ][i].centerJ; + } + } + + return -1.0; +} + +int FinderPatternFinder::addHorizontalCheckedResult(size_t startJ, size_t centerI, float centerJ) { + HorizontalCheckedResult result; + result.centerI = centerI; + result.centerJ = centerJ; + + _horizontalCheckedResult[startJ].push_back(result); + + return 1; +} + +#define CENTER_CHECK_TIME 3 + +/** + *

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 + */ +bool FinderPatternFinder::handlePossibleCenter(int* stateCount, size_t i, size_t j) { + CrossCheckState tmpHorizontalState = CURRENT_CHECK_STATE; + float centerJ = centerFromEnd(stateCount, j); + int stateCountTotal = stateCount[1] + stateCount[2] + stateCount[3]; + if (tmpHorizontalState == FinderPatternFinder::NORMAL) { + // 1:1:3:1:1 + stateCountTotal = stateCountTotal + stateCount[0] + stateCount[4]; + } else if (tmpHorizontalState == FinderPatternFinder::LEFT_SPILL) { + // n:1:3:1:1 + stateCountTotal = stateCountTotal + stateCount[1] + stateCount[4]; + } else if (tmpHorizontalState == FinderPatternFinder::RIHGT_SPILL) { + // 1:1:3:1:n + stateCountTotal = stateCountTotal + stateCount[0] + stateCount[3]; + } + float estimatedHorizontalModuleSize = (float)stateCountTotal / 7.0f; + + float estimatedVerticalModuleSize; + + // try different size according to the estimatedHorizontalModuleSize + float tolerateModuleSize = + estimatedHorizontalModuleSize > 4.0 ? estimatedHorizontalModuleSize / 2.0f : 1.0f; + float possbileCenterJs[7] = {centerJ, + centerJ - tolerateModuleSize, + centerJ + tolerateModuleSize, + centerJ - 2 * tolerateModuleSize, + centerJ + 2 * tolerateModuleSize, + centerJ - 3 * tolerateModuleSize, + centerJ + 3 * tolerateModuleSize}; + int image_height = image_->getHeight(); + int image_width = image_->getWidth(); + for (int k = 0; k < CENTER_CHECK_TIME; k++) { + float possibleCenterJ = possbileCenterJs[k]; + if (possibleCenterJ < 0 || possibleCenterJ >= image_width) continue; + float centerI = crossCheckVertical(i, (size_t)possibleCenterJ, stateCount[2], + stateCountTotal, estimatedVerticalModuleSize); + + if (!isnan(centerI) && centerI >= 0.0) { + CrossCheckState tmpVerticalState = CURRENT_CHECK_STATE; + + float moduleSizeDiff = abs(estimatedHorizontalModuleSize - estimatedVerticalModuleSize); + + if (moduleSizeDiff > estimatedHorizontalModuleSize || + moduleSizeDiff > estimatedVerticalModuleSize) + return false; + + tolerateModuleSize = + estimatedVerticalModuleSize > 4.0 ? estimatedVerticalModuleSize / 2.0f : 1.0f; + + float possbileCenterIs[7] = {centerI, + centerI - tolerateModuleSize, + centerI + tolerateModuleSize, + centerI - 2 * tolerateModuleSize, + centerI + 2 * tolerateModuleSize, + centerI - 3 * tolerateModuleSize, + centerI + 3 * tolerateModuleSize}; + + for (int l = 0; l < CENTER_CHECK_TIME; l++) { + float possibleCenterI = possbileCenterIs[l]; + if (possibleCenterI < 0 || possibleCenterI >= image_height) continue; + // Re-cross check + float reEstimatedHorizontalModuleSize; + float cJ = hasHorizontalCheckedResult(centerJ, possibleCenterI); + + if (!isnan(cJ) && cJ >= 0.0) { + centerJ = cJ; + } else { + cJ = centerJ; + + float ccj = + crossCheckHorizontal((size_t)cJ, (size_t)possibleCenterI, stateCount[2], + stateCountTotal, reEstimatedHorizontalModuleSize); + + if (!isnan(ccj)) { + centerJ = ccj; + addHorizontalCheckedResult(cJ, possibleCenterI, ccj); + } + } + if (!isnan(centerJ)) { + tryToPushToCenters( + centerI, centerJ, + (estimatedHorizontalModuleSize + estimatedVerticalModuleSize) / 2.0, + tmpHorizontalState, tmpVerticalState); + return true; + } + } + } + } + + return false; +} + +// return the 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 the image. +int FinderPatternFinder::findRowSkip() { + int max = possibleCenters_.size(); + if (max <= 1) { + return 0; + } + + if (max <= compared_finder_counts) return 0; + + Ref firstConfirmedCenter, secondConfirmedCenter; + + for (int i = 0; i < max - 1; i++) { + firstConfirmedCenter = possibleCenters_[i]; + if (firstConfirmedCenter->getCount() >= CENTER_QUORUM) { + float firstModuleSize = firstConfirmedCenter->getEstimatedModuleSize(); + int j_start = (i < compared_finder_counts) ? compared_finder_counts : i + 1; + for (int j = j_start; j < max; j++) { + secondConfirmedCenter = possibleCenters_[j]; + if (secondConfirmedCenter->getCount() >= CENTER_QUORUM) { + float secondModuleSize = secondConfirmedCenter->getEstimatedModuleSize(); + float moduleSizeDiff = abs(firstModuleSize - secondModuleSize); + if (moduleSizeDiff < 1.0f) { + hasSkipped_ = true; + return (int)(abs(firstConfirmedCenter->getX() - + secondConfirmedCenter->getX()) - + abs(firstConfirmedCenter->getY() - + secondConfirmedCenter->getY())) / + 2; + } + } + } + } + } + + compared_finder_counts = max; + + return 0; +} + +// return the 3 finder patterns from our list of candidates. The "best" are +// those that have been detected at least #CENTER_QUORUM times, and whose module +// size differs from the average among those patterns the least. // +vector> FinderPatternFinder::selectBestPatterns(ErrorHandler& err_handler) { + size_t startSize = possibleCenters_.size(); + + if (startSize < 3) { + // Couldn't find enough finder patterns + err_handler = ReaderErrorHandler("Could not find three finder patterns"); + return vector>(); + } + + vector> result(3); + + if (startSize == 3) { + result[0] = possibleCenters_[0]; + result[1] = possibleCenters_[1]; + result[2] = possibleCenters_[2]; + return result; + } + + sort(possibleCenters_.begin(), possibleCenters_.end(), CountComparator()); + if ((possibleCenters_[2]->getCount() - possibleCenters_[3]->getCount()) > 1 && + possibleCenters_[2]->getCount() > 1) { + result[0] = possibleCenters_[0]; + result[1] = possibleCenters_[1]; + result[2] = possibleCenters_[2]; + return result; + } else if (possibleCenters_[3]->getCount() > 1) { + float totalModuleSize = 0.0f; + for (int i = 0; i < 4; i++) { + totalModuleSize += possibleCenters_[i]->getEstimatedModuleSize(); + } + float everageModuleSize = totalModuleSize / 4.0f; + float maxDiffModuleSize = 0.0f; + int maxID = 0; + for (int i = 0; i < 4; i++) { + float diff = abs(possibleCenters_[i]->getEstimatedModuleSize() - everageModuleSize); + if (diff > maxDiffModuleSize) { + maxDiffModuleSize = diff; + maxID = i; + } + } + switch (maxID) { + case 0: + result[0] = possibleCenters_[1]; + result[1] = possibleCenters_[2]; + result[2] = possibleCenters_[3]; + break; + case 1: + result[0] = possibleCenters_[0]; + result[1] = possibleCenters_[2]; + result[2] = possibleCenters_[3]; + break; + case 2: + result[0] = possibleCenters_[0]; + result[1] = possibleCenters_[1]; + result[2] = possibleCenters_[3]; + break; + default: + result[0] = possibleCenters_[0]; + result[1] = possibleCenters_[1]; + result[2] = possibleCenters_[2]; + break; + } + + return result; + } else if (possibleCenters_[1]->getCount() > 1 && possibleCenters_[2]->getCount() == 1) { + vector> possibleThirdCenter; + float possibleModuleSize = (possibleCenters_[0]->getEstimatedModuleSize() + + possibleCenters_[1]->getEstimatedModuleSize()) / + 2.0f; + for (size_t i = 2; i < startSize; i++) { + if (abs(possibleCenters_[i]->getEstimatedModuleSize() - possibleModuleSize) < + 0.5 * possibleModuleSize) + possibleThirdCenter.push_back(possibleCenters_[i]); + } + float longestSide = 0.0f; + size_t longestId = 0; + for (size_t i = 0; i < possibleThirdCenter.size(); i++) { + float tmpLongSide = 0.0f; + if (checkIsoscelesRightTriangle(possibleCenters_[0], possibleCenters_[1], + possibleThirdCenter[i], tmpLongSide)) { + if (tmpLongSide >= longestSide) { + longestSide = tmpLongSide; + longestId = i; + } + } + } + result[0] = possibleCenters_[0]; + result[1] = possibleCenters_[1]; + + // Error with decoding + if (longestId >= possibleThirdCenter.size()) { + err_handler = ReaderErrorHandler("Not find any available possibleThirdCenter"); + return vector>(); + } else { + result[2] = possibleThirdCenter[longestId]; + } + + return result; + } + + // Filter outlier possibilities whose module size is too different + if (startSize > 3) { + // But we can only afford to do so if we have at least 4 possibilities + // to choose from + float totalModuleSize = 0.0f; + float square = 0.0f; + for (size_t i = 0; i < startSize; i++) { + float size = possibleCenters_[i]->getEstimatedModuleSize(); + totalModuleSize += size; + square += size * size; + } + float average = totalModuleSize / (float)startSize; + float stdDev = (float)sqrt(square / startSize - average * average); + + sort(possibleCenters_.begin(), possibleCenters_.end(), + FurthestFromAverageComparator(average)); + + // float limit = max(0.2f * average, stdDev); + float limit = max(0.5f * average, stdDev); + + for (size_t i = 0; i < possibleCenters_.size() && possibleCenters_.size() > 3; i++) { + if (abs(possibleCenters_[i]->getEstimatedModuleSize() - average) > limit) { + possibleCenters_.erase(possibleCenters_.begin() + i); + i--; + } + } + } + + int tryHardPossibleCenterSize = 15; + int possibleCenterSize = 12; + + if (possibleCenters_.size() > size_t(tryHardPossibleCenterSize)) { + sort(possibleCenters_.begin(), possibleCenters_.end(), CountComparator()); + possibleCenters_.erase(possibleCenters_.begin() + tryHardPossibleCenterSize, + possibleCenters_.end()); + } else if (possibleCenters_.size() > size_t(possibleCenterSize)) { + sort(possibleCenters_.begin(), possibleCenters_.end(), CountComparator()); + possibleCenters_.erase(possibleCenters_.begin() + possibleCenterSize, + possibleCenters_.end()); + } + + if (possibleCenters_.size() >= 6) { + sort(possibleCenters_.begin(), possibleCenters_.end(), XComparator()); + possibleCenters_.erase(possibleCenters_.begin() + 4, possibleCenters_.end() - 2); + sort(possibleCenters_.begin(), possibleCenters_.begin() + 4, YComparator()); + possibleCenters_.erase(possibleCenters_.begin() + 1, possibleCenters_.begin() + 3); + sort(possibleCenters_.end() - 2, possibleCenters_.end(), YComparator()); + possibleCenters_.erase(possibleCenters_.end() - 1, possibleCenters_.end()); + } else if (possibleCenters_.size() > 3) { + // Throw away all but those first size candidate points we found. + float totalModuleSize = 0.0f; + for (size_t i = 0; i < possibleCenters_.size(); i++) { + float size = possibleCenters_[i]->getEstimatedModuleSize(); + totalModuleSize += size; + } + float average = totalModuleSize / (float)possibleCenters_.size(); + sort(possibleCenters_.begin(), possibleCenters_.end(), CenterComparator(average)); + possibleCenters_.erase(possibleCenters_.begin() + 3, possibleCenters_.end()); + } + + result[0] = possibleCenters_[0]; + result[1] = possibleCenters_[1]; + result[2] = possibleCenters_[2]; + + return result; +} + +vector> FinderPatternFinder::selectFileBestPatterns(ErrorHandler& err_handler) { + size_t startSize = possibleCenters_.size(); + + if (startSize < 3) { + // Couldn't find enough finder patterns + err_handler = ReaderErrorHandler("Could not find three finder patterns"); + return vector>(); + } + + vector> result(3); + + if (startSize == 3) { + result[0] = possibleCenters_[0]; + result[1] = possibleCenters_[1]; + result[2] = possibleCenters_[2]; + return result; + } + + sort(possibleCenters_.begin(), possibleCenters_.end(), BestComparator()); + + result[0] = possibleCenters_[0]; + result[1] = possibleCenters_[1]; + result[2] = possibleCenters_[2]; + + for (size_t i = 0; i < possibleCenters_.size() - 2; ++i) { + float tmpLongSide = 0; + + int iCountDiff = 0; + float fModuleSizeDiff = 0; + for (size_t j = 0; j < 3; ++j) { + iCountDiff += abs(possibleCenters_[i + j]->getCount() - + possibleCenters_[i + ((j + 1) % 3)]->getCount()); + fModuleSizeDiff += fabs(possibleCenters_[i + j]->getEstimatedModuleSize() - + possibleCenters_[i + ((j + 1) % 3)]->getEstimatedModuleSize()); + } + + if (iCountDiff > 2) continue; + if (fModuleSizeDiff > 5) continue; + + if (checkIsoscelesRightTriangle(possibleCenters_[i], possibleCenters_[i + 1], + possibleCenters_[i + 2], tmpLongSide)) { + result[0] = possibleCenters_[i]; + result[1] = possibleCenters_[i + 1]; + result[2] = possibleCenters_[i + 2]; + + break; + } + } + + return result; +} + +// Orders an array of three patterns in an order [A,B,C] such that +// AB> FinderPatternFinder::orderBestPatterns( + vector> patterns) { + // Find distances between pattern centers + float abDistance = distance(patterns[0], patterns[1]); + float bcDistance = distance(patterns[1], patterns[2]); + float acDistance = distance(patterns[0], patterns[2]); + + Ref topLeft; + Ref topRight; + Ref bottomLeft; + // Assume one closest to other two is top left; + // topRight and bottomLeft will just be guesses below at first + if (bcDistance >= abDistance && bcDistance >= acDistance) { + topLeft = patterns[0]; + topRight = patterns[1]; + bottomLeft = patterns[2]; + } else if (acDistance >= bcDistance && acDistance >= abDistance) { + topLeft = patterns[1]; + topRight = patterns[0]; + bottomLeft = patterns[2]; + } else { + topLeft = patterns[2]; + topRight = patterns[0]; + bottomLeft = patterns[1]; + } + + // Use cross product to figure out which of other1/2 is the bottom left + // pattern. The vector "top_left -> bottom_left" x "top_left -> top_right" + // should yield a vector with positive z component + if ((bottomLeft->getY() - topLeft->getY()) * (topRight->getX() - topLeft->getX()) < + (bottomLeft->getX() - topLeft->getX()) * (topRight->getY() - topLeft->getY())) { + Ref temp = topRight; + topRight = bottomLeft; + bottomLeft = temp; + } + + vector> results(3); + results[0] = bottomLeft; + results[1] = topLeft; + results[2] = topRight; + + return results; +} + +bool FinderPatternFinder::checkIsoscelesRightTriangle(Ref centerA, + Ref centerB, + Ref centerC, float& longSide) { + float shortSide1, shortSide2; + FinderPatternInfo::calculateSides(centerA, centerB, centerC, longSide, shortSide1, shortSide2); + + auto minAmongThree = [](float a, float b, float c) { return min(min(a, b), c); }; + auto maxAmongThree = [](float a, float b, float c) { return max(max(a, b), c); }; + + float shortSideSqrt1 = sqrt(shortSide1); + float shortSideSqrt2 = sqrt(shortSide2); + float longSideSqrt = sqrt(longSide); + auto minSide = minAmongThree(shortSideSqrt1, shortSideSqrt2, longSideSqrt); + auto maxModuleSize = + maxAmongThree(centerA->getEstimatedModuleSize(), centerB->getEstimatedModuleSize(), + centerC->getEstimatedModuleSize()); + // if edge length smaller than 14 * module size + if (minSide <= maxModuleSize * 14) return false; + + float CosLong = (shortSide1 + shortSide2 - longSide) / (2 * shortSideSqrt1 * shortSideSqrt2); + float CosShort1 = (longSide + shortSide1 - shortSide2) / (2 * longSideSqrt * shortSideSqrt1); + float CosShort2 = (longSide + shortSide2 - shortSide1) / (2 * longSideSqrt * shortSideSqrt2); + + if (abs(CosLong) > FP_RIGHT_ANGLE || + (CosShort1 < FP_SMALL_ANGLE2 || CosShort1 > FP_SMALL_ANGLE1) || + (CosShort2 < FP_SMALL_ANGLE2 || CosShort2 > FP_SMALL_ANGLE1)) { + return false; + } + + return true; +} + +// return distance between two points +float FinderPatternFinder::distance(Ref p1, Ref p2) { + float dx = p1->getX() - p2->getX(); + float dy = p1->getY() - p2->getY(); + return (float)sqrt(dx * dx + dy * dy); +} + +FinderPatternFinder::FinderPatternFinder(Ref image, Ref block) + : finder_time(0), + compared_finder_counts(0), + image_(image), + possibleCenters_(), + hasSkipped_(false), + block_(block) { + CURRENT_CHECK_STATE = FinderPatternFinder::NORMAL; +} + +Ref FinderPatternFinder::getImage() { return image_; } + +vector>& FinderPatternFinder::getPossibleCenters() { return possibleCenters_; } + +} // namespace qrcode +} // namespace zxing \ No newline at end of file diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_finder.hpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_finder.hpp new file mode 100644 index 0000000..442198b --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_finder.hpp @@ -0,0 +1,136 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DETECTOR_FINDER_PATTERN_FINDER_HPP_ +#define __ZXING_QRCODE_DETECTOR_FINDER_PATTERN_FINDER_HPP_ + +#include "../../common/bitmatrix.hpp" +#include "../../common/counted.hpp" +#include "../../common/unicomblock.hpp" +#include "../../errorhandler.hpp" +#include "finder_pattern.hpp" +#include "finder_pattern_info.hpp" +using zxing::ErrorHandler; +using zxing::ReaderErrorHandler; + +namespace zxing { + +class DecodeHints; + +namespace qrcode { + +class FinderPatternFinder { +public: + enum CrossCheckState { + NORMAL = 0, + LEFT_SPILL = 1, + RIHGT_SPILL = 2, + LEFT_RIGHT_SPILL = 3, + NOT_PATTERN = 4, + }; + +private: + static int CENTER_QUORUM; + static int MIN_SKIP; + static int MAX_MODULES; + static int INTEGER_MATH_SHIFT; + static int FP_INPUT_CNN_MAX_NUM; + static int FP_IS_SELECT_BEST; + static int FP_IS_SELECT_FILE_BEST; + static int FP_INPUT_MAX_NUM; + static int FP_FILTER_SIZE; + static int FPS_CLUSTER_MAX; + static int FPS_RESULT_MAX; + static int K_FACTOR; + + static float FPS_MS_VAL; + static float FP_COUNT_MIN; + static float FP_MS_MIN; + static float FP_RIGHT_ANGLE; + static float FP_SMALL_ANGLE1; + static float FP_SMALL_ANGLE2; + static float QR_MIN_FP_AREA_ERR; + static float QR_MIN_FP_MS_ERR; + static int QR_MIN_FP_ACCEPT; + + int finder_time; + CrossCheckState CURRENT_CHECK_STATE; + int compared_finder_counts; + + struct HorizontalCheckedResult { + size_t centerI; + float centerJ; + }; + + vector > _horizontalCheckedResult; + + // INI CONFIG + +protected: + Ref image_; + std::vector > possibleCenters_; + + bool hasSkipped_; + Ref block_; + + /** stateCount must be int[5] */ + float centerFromEnd(int* stateCount, int end); + // check if satisfies finder pattern + bool foundPatternCross(int* stateCount); + + // try to insert to possibleCenters_ + int getStateCountTotal(int* stateCount, const CrossCheckState& check_state); + bool tryToPushToCenters(float posX, float posY, float estimatedModuleSize, + CrossCheckState horizontalState = FinderPatternFinder::NORMAL, + CrossCheckState verticalState = FinderPatternFinder::NORMAL); + bool crossCheckDiagonal(int startI, int centerJ, int maxCount, int originalStateCountTotal); + float crossCheckVertical(size_t startI, size_t centerJ, int maxCount, + int originalStateCountTota, float& estimatedVerticalModuleSize); + float crossCheckHorizontal(size_t startJ, size_t centerI, int maxCount, + int originalStateCountTotal, float& estimatedHorizontalModuleSize); + + float hasHorizontalCheckedResult(size_t startJ, size_t centerI); + int addHorizontalCheckedResult(size_t startJ, size_t centerI, float centerJ); + int getMinModuleSize(); + + bool isEqualResult(Ref src, Ref dst); + + /** stateCount must be int[5] */ + bool handlePossibleCenter(int* stateCount, size_t i, size_t j); + int findRowSkip(); + + std::vector > selectBestPatterns(ErrorHandler& err_handler); + std::vector > selectFileBestPatterns(ErrorHandler& err_handler); + std::vector > orderBestPatterns(std::vector > patterns); + + vector > getPatternInfosFileMode(DecodeHints const& hints, + ErrorHandler& err_handler); + + bool IsPossibleFindPatterInfo(Ref a, Ref b, Ref c); + void PushToResult(Ref a, Ref b, Ref c, + vector >& patternInfos); + + Ref getImage(); + std::vector >& getPossibleCenters(); + +public: + void InitConfig(); + float distance(Ref p1, Ref p2); + FinderPatternFinder(Ref image, Ref block); + + std::vector > find(DecodeHints const& hints, ErrorHandler& err_handler); + + bool checkIsoscelesRightTriangle(Ref centerA, Ref centerB, + Ref centerC, float& longSide); +}; +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DETECTOR_FINDER_PATTERN_FINDER_HPP_ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_info.cpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_info.cpp new file mode 100644 index 0000000..2e121f9 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_info.cpp @@ -0,0 +1,96 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "finder_pattern_info.hpp" + +namespace zxing { +namespace qrcode { +FinderPatternInfo::FinderPatternInfo(std::vector > patternCenters) + : bottomLeft_(patternCenters[0]), + topLeft_(patternCenters[1]), + topRight_(patternCenters[2]), + possibleFix_(0) { + estimateFinderPatternInfo(); +} + +Ref FinderPatternInfo::getBottomLeft() { return bottomLeft_; } + +Ref FinderPatternInfo::getTopLeft() { return topLeft_; } + +Ref FinderPatternInfo::getTopRight() { return topRight_; } + + +float FinderPatternInfo::getPossibleFix() { return possibleFix_; } + +float FinderPatternInfo::getAnglePossibleFix() { return anglePossibleFix_; } + +// bottomLeft_ => centerA +void FinderPatternInfo::calculateSides(Ref centerA, Ref centerB, + Ref centerC, float &longSide, + float &shortSide1, float &shortSide2) { + float a_m_b_x = centerA->getX() - centerB->getX(); + float a_m_b_y = centerA->getY() - centerB->getY(); + float ab_s = a_m_b_x * a_m_b_x + a_m_b_y * a_m_b_y; + float a_m_c_x = centerA->getX() - centerC->getX(); + float a_m_c_y = centerA->getY() - centerC->getY(); + float ac_s = a_m_c_x * a_m_c_x + a_m_c_y * a_m_c_y; + float b_m_c_x = centerB->getX() - centerC->getX(); + float b_m_c_y = centerB->getY() - centerC->getY(); + float bc_s = b_m_c_x * b_m_c_x + b_m_c_y * b_m_c_y; + + if (ab_s > bc_s && ab_s > ac_s) { + longSide = ab_s; + shortSide1 = ac_s; + shortSide2 = bc_s; + + } else if (bc_s > ab_s && bc_s > ac_s) { + longSide = bc_s; + shortSide1 = ab_s; + shortSide2 = ac_s; + } else { + longSide = ac_s; + shortSide1 = ab_s; + shortSide2 = bc_s; + } +} +void FinderPatternInfo::estimateFinderPatternInfo() { + float longSide, shortSide1, shortSide2; + calculateSides(bottomLeft_, topLeft_, topRight_, longSide, shortSide1, shortSide2); + + float CosLong = + (shortSide1 + shortSide2 - longSide) / (2 * sqrt(shortSide1) * sqrt(shortSide2)); + float CosShort1 = + (longSide + shortSide1 - shortSide2) / (2 * sqrt(longSide) * sqrt(shortSide1)); + float CosShort2 = + (longSide + shortSide2 - shortSide1) / (2 * sqrt(longSide) * sqrt(shortSide2)); + + float fAngleLong = acos(CosLong) * 180 / acos(-1.0); + float fAngleShort1 = acos(CosShort1) * 180 / acos(-1.0); + float fAngleShort2 = acos(CosShort2) * 180 / acos(-1.0); + if (fAngleShort1 < fAngleShort2) swap(fAngleShort1, fAngleShort2); + + float fLongDiff = fabs(fAngleLong - 90); + float fLongScore = 100.0 - fLongDiff; + + float fShortDiff = std::max(fabs(fAngleShort1 - 45), fabs(fAngleShort2 - 45)); + float fShortScore = 100.0 - 2 * fShortDiff; + + float fFinalScore = std::min(fShortScore, fLongScore); + + anglePossibleFix_ = fFinalScore / 100.0; + + int totalCount = (bottomLeft_->getCount() + topLeft_->getCount() + topRight_->getCount()); + + float fCountScore = (max(3, min(totalCount, 10)) - 3) / (10.0 - 3.0); + + possibleFix_ = anglePossibleFix_ * 0.5 + fCountScore * 0.5; +} +} // namespace qrcode +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_info.hpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_info.hpp new file mode 100644 index 0000000..223524e --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/finder_pattern_info.hpp @@ -0,0 +1,46 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DETECTOR_FINDER_PATTERN_INFO_HPP_ +#define __ZXING_QRCODE_DETECTOR_FINDER_PATTERN_INFO_HPP_ + +#include "../../common/array.hpp" +#include "../../common/counted.hpp" +#include "finder_pattern.hpp" + +namespace zxing { +namespace qrcode { + +class FinderPatternInfo : public Counted { +private: + Ref bottomLeft_; + Ref topLeft_; + Ref topRight_; + float possibleFix_; + float anglePossibleFix_; + +public: + explicit FinderPatternInfo(std::vector > patternCenters); + + Ref getBottomLeft(); + Ref getTopLeft(); + Ref getTopRight(); + void estimateFinderPatternInfo(); + float getPossibleFix(); + float getAnglePossibleFix(); + // to void code duplicated + static void calculateSides(Ref centerA, Ref centerB, + Ref centerC, float &longSide, float &shortSide1, + float &shortSide2); +}; +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DETECTOR_FINDER_PATTERN_INFO_HPP_ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/pattern_result.cpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/pattern_result.cpp new file mode 100644 index 0000000..b44349e --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/pattern_result.cpp @@ -0,0 +1,28 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../../precomp.hpp" +#include "pattern_result.hpp" + + +using zxing::Ref; +using zxing::ResultPoint; +using zxing::qrcode::FinderPattern; +using zxing::qrcode::FinderPatternInfo; +using zxing::qrcode::PatternResult; + +PatternResult::PatternResult(Ref info) { + finderPatternInfo = info; + possibleAlignmentPatterns.clear(); +} + +void PatternResult::setConfirmedAlignmentPattern(int index) { + if (index >= int(possibleAlignmentPatterns.size())) return; + confirmedAlignmentPattern = possibleAlignmentPatterns[index]; +} diff --git a/modules/wechat_qrcode/src/zxing/qrcode/detector/pattern_result.hpp b/modules/wechat_qrcode/src/zxing/qrcode/detector/pattern_result.hpp new file mode 100644 index 0000000..93a4f2b --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/detector/pattern_result.hpp @@ -0,0 +1,48 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_DETECTOR_PATTERN_RESULT_HPP_ +#define __ZXING_QRCODE_DETECTOR_PATTERN_RESULT_HPP_ + +#include "../../common/array.hpp" +#include "../../common/bitmatrix.hpp" +#include "../../common/counted.hpp" +#include "../../common/detector_result.hpp" +#include "../../resultpoint.hpp" +#include "alignment_pattern.hpp" +#include "finder_pattern.hpp" +#include "finder_pattern_info.hpp" +namespace zxing { +namespace qrcode { +class PatternResult : public Counted { +public: + Ref finderPatternInfo; + vector > possibleAlignmentPatterns; + Ref confirmedAlignmentPattern; + int possibleDimension; + // vector possibleDimensions; + unsigned int possibleVersion; + float possibleFix; + float possibleModuleSize; + + explicit PatternResult(Ref info); + void setConfirmedAlignmentPattern(int index); + int getPossibleAlignmentCount() { return possibleAlignmentPatterns.size(); } + // int getPossibleDimensionCount(); +public: + unsigned int getPossibleVersion() { return possibleVersion; } + float getPossibleFix() { return possibleFix; } + float getPossibleModuleSize() { return possibleModuleSize; } + int getDimension() { return possibleDimension; } +}; +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_DETECTOR_PATTERN_RESULT_HPP_ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/error_correction_level.cpp b/modules/wechat_qrcode/src/zxing/qrcode/error_correction_level.cpp new file mode 100644 index 0000000..1b2c63d --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/error_correction_level.cpp @@ -0,0 +1,44 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "error_correction_level.hpp" +using zxing::ErrorHandler; + +namespace zxing { +namespace qrcode { + +ErrorCorrectionLevel::ErrorCorrectionLevel(int inOrdinal, int bits, char const* name) + : ordinal_(inOrdinal), bits_(bits), name_(name) {} + +int ErrorCorrectionLevel::ordinal() const { return ordinal_; } + +int ErrorCorrectionLevel::bits() const { return bits_; } + +string const& ErrorCorrectionLevel::name() const { return name_; } + +ErrorCorrectionLevel::operator string const &() const { return name_; } + +ErrorCorrectionLevel& ErrorCorrectionLevel::forBits(int bits, ErrorHandler& err_handler) { + if (bits < 0 || bits >= N_LEVELS) { + err_handler = zxing::ReaderErrorHandler("Ellegal error correction level bits"); + return *FOR_BITS[0]; + } + return *FOR_BITS[bits]; +} + +ErrorCorrectionLevel ErrorCorrectionLevel::L(0, 0x01, "L"); +ErrorCorrectionLevel ErrorCorrectionLevel::M(1, 0x00, "M"); +ErrorCorrectionLevel ErrorCorrectionLevel::Q(2, 0x03, "Q"); +ErrorCorrectionLevel ErrorCorrectionLevel::H(3, 0x02, "H"); +ErrorCorrectionLevel* ErrorCorrectionLevel::FOR_BITS[] = {&M, &L, &H, &Q}; +int ErrorCorrectionLevel::N_LEVELS = 4; + +} // namespace qrcode +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/qrcode/error_correction_level.hpp b/modules/wechat_qrcode/src/zxing/qrcode/error_correction_level.hpp new file mode 100644 index 0000000..67344a8 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/error_correction_level.hpp @@ -0,0 +1,44 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_ERROR_CORRECTION_LEVEL_HPP__ +#define __ZXING_QRCODE_ERROR_CORRECTION_LEVEL_HPP__ + +#include "../errorhandler.hpp" + +namespace zxing { +namespace qrcode { + +class ErrorCorrectionLevel { +private: + int ordinal_; + int bits_; + std::string name_; + ErrorCorrectionLevel(int inOrdinal, int bits, char const* name); + static ErrorCorrectionLevel* FOR_BITS[]; + static int N_LEVELS; + +public: + static ErrorCorrectionLevel L; + static ErrorCorrectionLevel M; + static ErrorCorrectionLevel Q; + static ErrorCorrectionLevel H; + + int ordinal() const; + int bits() const; + std::string const& name() const; + operator std::string const &() const; + + static ErrorCorrectionLevel& forBits(int bits, ErrorHandler& err_handler); +}; +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_ERROR_CORRECTION_LEVEL_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/format_information.cpp b/modules/wechat_qrcode/src/zxing/qrcode/format_information.cpp new file mode 100644 index 0000000..5395f79 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/format_information.cpp @@ -0,0 +1,114 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "format_information.hpp" + +#include + +using zxing::ErrorHandler; + +namespace zxing { +namespace qrcode { + +int FormatInformation::FORMAT_INFO_MASK_QR = 0x5412; +int FormatInformation::FORMAT_INFO_DECODE_LOOKUP[][2] = { + {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}, +}; +int FormatInformation::N_FORMAT_INFO_DECODE_LOOKUPS = 32; + +int FormatInformation::BITS_SET_IN_HALF_BYTE[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; + +FormatInformation::FormatInformation(int formatInfo, float possiableFix, ErrorHandler& err_handler) + : errorCorrectionLevel_(ErrorCorrectionLevel::forBits((formatInfo >> 3) & 0x03, err_handler)), + dataMask_((char)(formatInfo & 0x07)) { + possiableFix_ = possiableFix; + if (err_handler.ErrCode()) return; +} + +ErrorCorrectionLevel& FormatInformation::getErrorCorrectionLevel() { return errorCorrectionLevel_; } + +char FormatInformation::getDataMask() { return dataMask_; } + +float FormatInformation::getPossiableFix() { return possiableFix_; } + +int FormatInformation::numBitsDiffering(int a, int b) { + a ^= b; + return BITS_SET_IN_HALF_BYTE[a & 0x0F] + BITS_SET_IN_HALF_BYTE[(a >> 4 & 0x0F)] + + BITS_SET_IN_HALF_BYTE[(a >> 8 & 0x0F)] + BITS_SET_IN_HALF_BYTE[(a >> 12 & 0x0F)] + + BITS_SET_IN_HALF_BYTE[(a >> 16 & 0x0F)] + BITS_SET_IN_HALF_BYTE[(a >> 20 & 0x0F)] + + BITS_SET_IN_HALF_BYTE[(a >> 24 & 0x0F)] + BITS_SET_IN_HALF_BYTE[(a >> 28 & 0x0F)]; +} + +Ref FormatInformation::decodeFormatInformation(int maskedFormatInfo1, + int maskedFormatInfo2) { + Ref result(doDecodeFormatInformation(maskedFormatInfo1, maskedFormatInfo2)); + if (result != 0) { + return result; + } + // 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); +} +Ref FormatInformation::doDecodeFormatInformation(int maskedFormatInfo1, + int maskedFormatInfo2) { + ErrorHandler err_handler; + int distance = numBitsDiffering(maskedFormatInfo1, maskedFormatInfo2); + float possiableFix_ = (16.0 - (distance > 16 ? 16 : distance)) / 16.0; + + // Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing + int bestDifference = std::numeric_limits::max(); + int bestFormatInfo = 0; + for (int i = 0; i < N_FORMAT_INFO_DECODE_LOOKUPS; i++) { + int* decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i]; + int targetInfo = decodeInfo[0]; + if (targetInfo == maskedFormatInfo1 || targetInfo == maskedFormatInfo2) { + // Found an exact match + Ref result( + new FormatInformation(decodeInfo[1], possiableFix_, err_handler)); + if (err_handler.ErrCode()) return Ref(); + return result; + } + 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; + } + } + } + if (bestDifference <= 3) { + Ref result( + new FormatInformation(bestFormatInfo, possiableFix_, err_handler)); + if (err_handler.ErrCode()) return Ref(); + return result; + } + Ref result; + return result; +} + +bool operator==(const FormatInformation& a, const FormatInformation& b) { + return &(a.errorCorrectionLevel_) == &(b.errorCorrectionLevel_) && a.dataMask_ == b.dataMask_; +} + +} // namespace qrcode +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/qrcode/format_information.hpp b/modules/wechat_qrcode/src/zxing/qrcode/format_information.hpp new file mode 100644 index 0000000..249c35b --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/format_information.hpp @@ -0,0 +1,48 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_FORMAT_INFORMATION_HPP__ +#define __ZXING_QRCODE_FORMAT_INFORMATION_HPP__ + +#include "../common/counted.hpp" +#include "../errorhandler.hpp" +#include "error_correction_level.hpp" + +namespace zxing { +namespace qrcode { + +class FormatInformation : public Counted { +private: + static int FORMAT_INFO_MASK_QR; + static int FORMAT_INFO_DECODE_LOOKUP[][2]; + static int N_FORMAT_INFO_DECODE_LOOKUPS; + static int BITS_SET_IN_HALF_BYTE[]; + + ErrorCorrectionLevel &errorCorrectionLevel_; + char dataMask_; + float possiableFix_; + + FormatInformation(int formatInfo, float possiableFix, ErrorHandler &err_handler); + +public: + static int numBitsDiffering(int a, int b); + static Ref decodeFormatInformation(int maskedFormatInfo1, + int maskedFormatInfo2); + static Ref doDecodeFormatInformation(int maskedFormatInfo1, + int maskedFormatInfo2); + ErrorCorrectionLevel &getErrorCorrectionLevel(); + char getDataMask(); + float getPossiableFix(); + friend bool operator==(const FormatInformation &a, const FormatInformation &b); +}; +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_FORMAT_INFORMATION_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/qrcode_reader.cpp b/modules/wechat_qrcode/src/zxing/qrcode/qrcode_reader.cpp new file mode 100644 index 0000000..64a6c08 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/qrcode_reader.cpp @@ -0,0 +1,491 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "qrcode_reader.hpp" +#include +#include "../common/bitarray.hpp" +#include "detector/detector.hpp" + + +using zxing::ErrorHandler; + +namespace zxing { +namespace qrcode { + +QRCodeReader::QRCodeReader() : decoder_() { + readerState_ = QRCodeReader::READER_START; + detectedDimension_ = -1; + lastDecodeTime_ = 0; + lastDecodeID_ = 0; + decodeID_ = 0; + lastPossibleAPCount_ = 0; + possibleAPCount_ = 0; + lastSamePossibleAPCountTimes_ = 0; + samePossibleAPCountTimes_ = 0; + lastRecommendedImageSizeType_ = 0; + recommendedImageSizeType_ = 0; + smoothMaxMultiple_ = 40; +} + +Ref QRCodeReader::decode(Ref image) { return decode(image, DecodeHints()); } + +Ref QRCodeReader::decode(Ref image, DecodeHints hints) { + // Binarize image using the Histogram Binarized method and be binarized + ErrorHandler err_handler; + Ref imageBitMatrix = image->getBlackMatrix(err_handler); + if (err_handler.ErrCode() || imageBitMatrix == NULL) return Ref(); + + Ref rst = decodeMore(image, imageBitMatrix, hints, err_handler); + if (err_handler.ErrCode() || rst == NULL) { + // black white mirro!!! + Ref invertedMatrix = image->getInvertedMatrix(err_handler); + if (err_handler.ErrCode() || invertedMatrix == NULL) return Ref(); + Ref tmp_rst = decodeMore(image, invertedMatrix, hints, err_handler); + if (err_handler.ErrCode() || tmp_rst == NULL) return Ref(); + return tmp_rst; + } + + return rst; +} + +Ref QRCodeReader::decodeMore(Ref image, Ref imageBitMatrix, + DecodeHints hints, ErrorHandler &err_handler) { + nowHints_ = hints; + std::string ept; + + if (imageBitMatrix == NULL) return Ref(); + image->m_poUnicomBlock->Init(); + image->m_poUnicomBlock->Reset(imageBitMatrix); + + for (int tryTimes = 0; tryTimes < 1; tryTimes++) { + Ref detector(new Detector(imageBitMatrix, image->m_poUnicomBlock)); + err_handler.Reset(); + + detector->detect(hints, err_handler); + if (err_handler.ErrCode()) { + err_handler = zxing::ReaderErrorHandler("error detect"); + setReaderState(detector->getState()); + ept = err_handler.ErrMsg(); + continue; + } + + setReaderState(detector->getState()); + + int possiblePatternCount = detector->getPossiblePatternCount(); + + if (possiblePatternCount <= 0) { + continue; + } + for (int i = 0; i < possiblePatternCount; i++) { + // filter and perserve the highest score. + Ref patternInfo = detector->getFinderPatternInfo(i); + setPatternFix(patternInfo->getPossibleFix()); + if (patternInfo->getAnglePossibleFix() < 0.6 && i) continue; + + int possibleAlignmentCount = 0; + possibleAlignmentCount = detector->getPossibleAlignmentCount(i); + if (possibleAlignmentCount < 0) continue; + + detectedDimension_ = detector->getDimension(i); + possibleModuleSize_ = detector->getPossibleModuleSize(i); + setPossibleAPCountByVersion(detector->getPossibleVersion(i)); + + vector needTryVariousDeimensions(possibleAlignmentCount, false); + for (int j = 0; j < possibleAlignmentCount; j++) { + ArrayRef > points; + err_handler.Reset(); + Ref detectorResult = + detector->getResultViaAlignment(i, j, detectedDimension_, err_handler); + if (err_handler.ErrCode()) { + ept = err_handler.ErrCode(); + setDecoderFix(decoder_.getPossibleFix(), points); + setReaderState(decoder_.getState()); + + if ((patternInfo->getPossibleFix() > 0.9 && decoder_.getPossibleFix() < 0.1)) { + needTryVariousDeimensions[j] = true; + } + continue; + } + + points = detectorResult->getPoints(); + Ref decoderResult( + decoder_.decode(detectorResult->getBits(), err_handler)); + if (err_handler.ErrCode()) { + ept = err_handler.ErrCode(); + setDecoderFix(decoder_.getPossibleFix(), points); + setReaderState(decoder_.getState()); + + if ((patternInfo->getPossibleFix() > 0.9 && decoder_.getPossibleFix() < 0.1)) { + needTryVariousDeimensions[j] = true; + } + continue; + } + + // If the code was mirrored: swap the bottom-left and the + // top-right points. + if (decoderResult->getOtherClassName() == "QRCodeDecoderMetaData") { + decoderResult->getOther()->applyMirroredCorrection(points); + } + + setDecoderFix(decoder_.getPossibleFix(), points); + setReaderState(decoder_.getState()); + + Ref result( + new Result(decoderResult->getText(), decoderResult->getRawBytes(), points, + decoderResult->getCharset(), decoderResult->getQRCodeVersion(), + decoderResult->getEcLevel(), decoderResult->getCharsetMode())); + setSuccFix(points); + + return result; + } + // try different dimentions + for (int j = 0; j < possibleAlignmentCount; j++) { + err_handler.Reset(); + ArrayRef > points; + if (needTryVariousDeimensions[j]) { + vector possibleDimensions = getPossibleDimentions(detectedDimension_); + for (size_t k = 1; k < possibleDimensions.size(); k++) { + err_handler.Reset(); + int dimension = possibleDimensions[k]; + + Ref detectorResult = + detector->getResultViaAlignment(i, j, dimension, err_handler); + if (err_handler.ErrCode() || detectorResult == NULL) { + ept = err_handler.ErrMsg(); + setDecoderFix(decoder_.getPossibleFix(), points); + setReaderState(decoder_.getState()); + continue; + } + + points = detectorResult->getPoints(); + Ref decoderResult( + decoder_.decode(detectorResult->getBits(), err_handler)); + if (err_handler.ErrCode() || decoderResult == NULL) { + ept = err_handler.ErrMsg(); + setDecoderFix(decoder_.getPossibleFix(), points); + setReaderState(decoder_.getState()); + continue; + } + + if (decoderResult->getOtherClassName() == "QRCodeDecoderMetaData") { + decoderResult->getOther()->applyMirroredCorrection(points); + } + + setDecoderFix(decoder_.getPossibleFix(), points); + setReaderState(decoder_.getState()); + + detectedDimension_ = possibleDimensions[k]; + Ref result(new Result( + decoderResult->getText(), decoderResult->getRawBytes(), points, + decoderResult->getCharset(), decoderResult->getQRCodeVersion(), + decoderResult->getEcLevel(), decoderResult->getCharsetMode())); + + setSuccFix(points); + return result; + } + } + } + } + } + return Ref(); +} + +vector QRCodeReader::getPossibleDimentions(int detectDimension) { + vector possibleDimentions; + possibleDimentions.clear(); + + if (detectDimension < 0) { + return possibleDimentions; + } + + possibleDimentions.push_back(detectDimension); + + if (detectDimension <= 169 && detectDimension >= 73) { + possibleDimentions.push_back(detectDimension + 4); + possibleDimentions.push_back(detectDimension - 4); + possibleDimentions.push_back(detectDimension - 8); + possibleDimentions.push_back(detectDimension + 8); + } else if (detectDimension <= 69 && detectDimension >= 45) { + possibleDimentions.push_back(detectDimension + 4); + possibleDimentions.push_back(detectDimension - 4); + } + + if (detectDimension == 19) { + possibleDimentions.push_back(21); + } + + return possibleDimentions; +} + +void QRCodeReader::setPossibleAPCountByVersion(unsigned int version) { + // cout<<"setPossibleAPCountByVersion"< input, Ref output, + int window) { + BitMatrix &imatrix = *input; + BitMatrix &omatrix = *output; + window >>= 1; + int count = 0; + int width = input->getWidth(); + int height = input->getHeight(); + int bitsize = imatrix.getRowBitsSize(); + + bool *jrowtoset = new bool[bitsize]; + + bool *jrow = NULL; + + jrow = NULL; + + unsigned int size = window * window; + + for (int j = (window + 1); j < (height - 1 - window); ++j) { + int y1 = j - window - 1; + int y2 = j + window; + + int offset1 = y1 * width; + int offset2 = y2 * width; + + jrow = imatrix.getRowBoolPtr(j); + + memcpy(jrowtoset, jrow, bitsize * sizeof(bool)); + + for (int i = (window + 1); i < (width - 1 - window); ++i) { + int x1 = i - window - 1; + int x2 = i + window; + unsigned int sum = integral[offset2 + x2] - integral[offset2 + x1] + + integral[offset1 + x2] - integral[offset1 + x1]; + bool b = jrow[i]; + bool result; + // the middle 1/3 contains informations of corner, these + // informations is useful for finding the finder pattern + int sum3 = 3 * sum; + if ((unsigned int)sum3 <= size) { + result = false; + } else if ((unsigned int)sum3 >= size * 2) { + result = true; + } else { + result = b; + } + + if (result) { + jrowtoset[i] = true; + } + count += (result ^ b) == 1 ? 1U : 0U; + } + omatrix.setRowBool(j, jrowtoset); + } + + delete[] jrowtoset; + return count; +} + +void QRCodeReader::initIntegralOld(unsigned int *integral, Ref input) { + BitMatrix &matrix = *input; + int width = input->getWidth(); + int height = input->getHeight(); + + bool *therow = NULL; + + therow = matrix.getRowBoolPtr(0); + + integral[0] = therow[0]; + + int *s = new int[width]; + + memset(s, 0, width * sizeof(int)); + + integral[0] = therow[0]; + for (int j = 1; j < width; j++) { + integral[j] = integral[j - 1] + therow[j]; + s[j] += therow[j]; + } + + int offset = width; + unsigned int prevSum = 0; + + for (int i = 1; i < height; i++) { + offset = i * width; + therow = matrix.getRowBoolPtr(i); + + integral[offset] = integral[offset - width] + therow[0]; + offset++; + + for (int j = 1; j < width; j++) { + s[j] += therow[j]; + integral[offset] = prevSum + s[j]; + prevSum = integral[offset]; + offset++; + } + } + + delete[] s; + + return; +} + +void QRCodeReader::initIntegral(unsigned int *integral, Ref input) { + BitMatrix &matrix = *input; + int width = input->getWidth(); + int height = input->getHeight(); + + bool *therow = NULL; + + therow = matrix.getRowBoolPtr(0); + + // first row only + int rs = 0; + for (int j = 0; j < width; j++) { + rs += therow[j]; + integral[j] = rs; + } + + // remaining cells are sum above and to the left + int offset = 0; + + for (int i = 1; i < height; ++i) { + therow = matrix.getRowBoolPtr(i); + + rs = 0; + + offset += width; + + for (int j = 0; j < width; ++j) { + rs += therow[j]; + integral[offset + j] = rs + integral[offset - width + j]; + } + } + + return; +} + +int QRCodeReader::getRecommendedImageSizeTypeInteral() { + if (time(0) - lastDecodeTime_ > 30) recommendedImageSizeType_ = 0; + return recommendedImageSizeType_; +} + +unsigned int QRCodeReader::getDecodeID() { return decodeID_; } + +void QRCodeReader::setDecodeID(unsigned int id) { + lastDecodeTime_ = time(0); + + decodeID_ = id; + if (decodeID_ != lastDecodeID_) { + lastDecodeID_ = decodeID_; + lastPossibleAPCount_ = possibleAPCount_; + lastSamePossibleAPCountTimes_ = samePossibleAPCountTimes_; + lastRecommendedImageSizeType_ = getRecommendedImageSizeTypeInteral(); + possibleAPCount_ = 0; + recommendedImageSizeType_ = 0; + } +} + +QRCodeReader::~QRCodeReader() {} +Decoder &QRCodeReader::getDecoder() { return decoder_; } + +unsigned int QRCodeReader::getPossibleAPType() { + int version = (detectedDimension_ - 21) / 4 + 1; + setPossibleAPCountByVersion(version); + return possibleAPCount_; +} +int QRCodeReader::getPossibleFixType() { return possibleQrcodeInfo_.possibleFix > 0.0 ? 1 : 0; } + +void QRCodeReader::setPatternFix(float possibleFix) { + possibleQrcodeInfo_.patternPossibleFix = possibleFix; +} + +void QRCodeReader::setDecoderFix(float possibleFix, ArrayRef > border) { + float realFix = possibleFix; + if (possibleQrcodeInfo_.possibleFix < realFix) { + possibleQrcodeInfo_.possibleFix = realFix; + possibleQrcodeInfo_.qrcodeBorder.clear(); + possibleQrcodeInfo_.possibleModuleSize = possibleModuleSize_; + if (border) { + for (int i = 0; i < 4; ++i) { + possibleQrcodeInfo_.qrcodeBorder.push_back(border[i]); + } + } + } +} +void QRCodeReader::setSuccFix(ArrayRef > border) { + possibleQrcodeInfo_.qrcodeBorder.clear(); + possibleQrcodeInfo_.possibleModuleSize = possibleModuleSize_; + if (border) { + for (int i = 0; i < 4; ++i) { + possibleQrcodeInfo_.qrcodeBorder.push_back(border[i]); + } + } +} + +void QRCodeReader::setReaderState(Detector::DetectorState state) { + switch (state) { + case Detector::START: + this->readerState_ = QRCodeReader::DETECT_START; + break; + case Detector::FINDFINDERPATTERN: + this->readerState_ = QRCodeReader::DETECT_FINDFINDERPATTERN; + break; + case Detector::FINDALIGNPATTERN: + this->readerState_ = QRCodeReader::DETECT_FINDALIGNPATTERN; + break; + } + return; +} +void QRCodeReader::setReaderState(Decoder::DecoderState state) { + switch (state) { + case Decoder::NOTSTART: + this->readerState_ = QRCodeReader::DETECT_FAILD; + break; + case Decoder::START: + if (this->readerState_ < QRCodeReader::DECODE_START) { + this->readerState_ = QRCodeReader::DECODE_START; + } + break; + case Decoder::READVERSION: + if (this->readerState_ < QRCodeReader::DECODE_READVERSION) { + this->readerState_ = QRCodeReader::DECODE_READVERSION; + } + break; + case Decoder::READERRORCORRECTIONLEVEL: + if (this->readerState_ < QRCodeReader::DECODE_READERRORCORRECTIONLEVEL) { + this->readerState_ = QRCodeReader::DECODE_READERRORCORRECTIONLEVEL; + } + break; + case Decoder::READCODEWORDSORRECTIONLEVEL: + if (this->readerState_ < QRCodeReader::DECODE_READCODEWORDSORRECTIONLEVEL) { + this->readerState_ = QRCodeReader::DECODE_READCODEWORDSORRECTIONLEVEL; + } + break; + case Decoder::FINISH: + if (this->readerState_ < QRCodeReader::DECODE_FINISH) { + this->readerState_ = QRCodeReader::DECODE_FINISH; + } + break; + } + return; +} +} // namespace qrcode +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/qrcode/qrcode_reader.hpp b/modules/wechat_qrcode/src/zxing/qrcode/qrcode_reader.hpp new file mode 100644 index 0000000..bb0ccf6 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/qrcode_reader.hpp @@ -0,0 +1,131 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_QRCODE_READER_HPP__ +#define __ZXING_QRCODE_QRCODE_READER_HPP__ + +#include "../decodehints.hpp" +#include "../errorhandler.hpp" +#include "../reader.hpp" +#include "decoder/decoder.hpp" +#include "decoder/qrcode_decoder_metadata.hpp" +#include "detector/detector.hpp" + +namespace zxing { +namespace qrcode { + +struct QBAR_QRCODE_DETECT_INFO { + int possibleFixIndex; + unsigned int possibleAPType; + + // QRCodeReader Info + float possibleFix; + float patternPossibleFix; + int pyramidLev; + float possibleModuleSize; + std::vector > qrcodeBorder; + + QBAR_QRCODE_DETECT_INFO() { clear(); } + + void clear() { + possibleFixIndex = -1; + possibleAPType = 0; + possibleModuleSize = 0; + + possibleFix = 0; + patternPossibleFix = 0; + pyramidLev = 0; + qrcodeBorder.clear(); + } +}; + +class QRCodeReader : public Reader { +public: + enum ReaderState { + READER_START = -1, + DETECT_START = 0, + DETECT_FINDFINDERPATTERN = 1, + DETECT_FINDALIGNPATTERN = 2, + DETECT_FAILD = 3, + DECODE_START = 4, + DECODE_READVERSION = 5, + DECODE_READERRORCORRECTIONLEVEL = 6, + DECODE_READCODEWORDSORRECTIONLEVEL = 7, + DECODE_FINISH = 8 + }; + +private: + Decoder decoder_; + int detectedDimension_; + ReaderState readerState_; + DecodeHints nowHints_; + +protected: + Decoder& getDecoder(); + +public: + QRCodeReader(); + virtual ~QRCodeReader(); + string name() override { return "qrcode"; } + + Ref decode(Ref image) override; + Ref decode(Ref image, DecodeHints hints) override; + + Ref decodeMore(Ref image, Ref imageBitMatrix, + DecodeHints hints, ErrorHandler& err_handler); + +private: + QBAR_QRCODE_DETECT_INFO possibleQrcodeInfo_; + +protected: + void setPossibleAPCountByVersion(unsigned int version); + int getRecommendedImageSizeTypeInteral(); + static void initIntegralOld(unsigned int* integral, Ref input); + static void initIntegral(unsigned int* integral, Ref input); + static int smooth(unsigned int* integral, Ref input, Ref output, + int window); + unsigned int lastDecodeTime_; + unsigned int lastDecodeID_; + unsigned int decodeID_; + int lastPossibleAPCount_; + int possibleAPCount_; + int possibleModuleSize_; + unsigned int lastSamePossibleAPCountTimes_; + unsigned int samePossibleAPCountTimes_; + unsigned int lastRecommendedImageSizeType_; + unsigned int recommendedImageSizeType_; + unsigned int smoothMaxMultiple_; + +public: + virtual unsigned int getDecodeID() override; + virtual void setDecodeID(unsigned int id) override; + virtual float getPossibleFix() override; + virtual unsigned int getPossibleAPType(); + virtual int getPossibleFixType(); + + void setReaderState(Detector::DetectorState state); + void setReaderState(Decoder::DecoderState state); + + void setPatternFix(float possibleFix); + void setDecoderFix(float possibleFix, ArrayRef > border); + void setSuccFix(ArrayRef > border); + + ReaderState getReaderState() { return this->readerState_; } + float calQrcodeArea(Ref detectorResult); + float calTriangleArea(Ref centerA, Ref centerB, + Ref centerC); + + vector getPossibleDimentions(int detectDimension); +}; + +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_QRCODE_READER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/qrcode/version.cpp b/modules/wechat_qrcode/src/zxing/qrcode/version.cpp new file mode 100644 index 0000000..1f08a6b --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/version.cpp @@ -0,0 +1,500 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../../precomp.hpp" +#include "version.hpp" +#include "format_information.hpp" + +#include +using std::numeric_limits; +using zxing::ErrorHandler; + +namespace zxing { +namespace qrcode { + +ECB::ECB(int count, int dataCodewords) : count_(count), dataCodewords_(dataCodewords) {} + +int ECB::getCount() { return count_; } + +int ECB::getDataCodewords() { return dataCodewords_; } + +ECBlocks::ECBlocks(int ecCodewords, ECB *ecBlocks) + : ecCodewords_(ecCodewords), ecBlocks_(1, ecBlocks) {} + +ECBlocks::ECBlocks(int ecCodewords, ECB *ecBlocks1, ECB *ecBlocks2) + : ecCodewords_(ecCodewords), ecBlocks_(1, ecBlocks1) { + ecBlocks_.push_back(ecBlocks2); +} + +int ECBlocks::getECCodewords() { return ecCodewords_; } + +std::vector &ECBlocks::getECBlocks() { return ecBlocks_; } + +ECBlocks::~ECBlocks() { + for (size_t i = 0; i < ecBlocks_.size(); i++) { + delete ecBlocks_[i]; + } +} + +unsigned int Version::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}; +int Version::N_VERSION_DECODE_INFOS = 34; + +vector > Version::VERSIONS; +static int N_VERSIONS = Version::buildVersions(); + +int Version::getVersionNumber() { return versionNumber_; } + +vector &Version::getAlignmentPatternCenters() { return alignmentPatternCenters_; } + +int Version::getTotalCodewords() { return totalCodewords_; } + +int Version::getDimensionForVersion(ErrorHandler &err_handler) { + if (versionNumber_ < 1 || versionNumber_ > N_VERSIONS) { + err_handler = zxing::ReaderErrorHandler("versionNumber must be between 1 and 40"); + return -1; + } + return 17 + 4 * versionNumber_; +} + +ECBlocks &Version::getECBlocksForLevel(ErrorCorrectionLevel &ecLevel) { + return *ecBlocks_[ecLevel.ordinal()]; +} + +static vector *intArray(size_t n...) { + va_list ap; + va_start(ap, n); + vector *result = new vector(n); + for (size_t i = 0; i < n; i++) { + (*result)[i] = va_arg(ap, int); + } + va_end(ap); + return result; +} + +Version *Version::getProvisionalVersionForDimension(int dimension, ErrorHandler &err_handler) { + if (dimension % 4 != 1) { + err_handler = zxing::FormatErrorHandler("dimension % 4 != 1"); + return NULL; + } + + // return Version::getVersionForNumber((dimension - 17) >> 2); + Version *version = Version::getVersionForNumber((dimension - 17) >> 2, err_handler); + if (err_handler.ErrCode()) { + err_handler = zxing::FormatErrorHandler("err format"); + return NULL; + } + return version; +} + +Version *Version::getVersionForNumber(int versionNumber, ErrorHandler &err_handler) { + if (versionNumber < 1 || versionNumber > N_VERSIONS) { + err_handler = zxing::ReaderErrorHandler("versionNumber must be between 1 and 40"); + return NULL; + } + return VERSIONS[versionNumber - 1]; +} + +Version::Version(int versionNumber, vector *alignmentPatternCenters, ECBlocks *ecBlocks1, + ECBlocks *ecBlocks2, ECBlocks *ecBlocks3, ECBlocks *ecBlocks4) + : versionNumber_(versionNumber), + alignmentPatternCenters_(*alignmentPatternCenters), + ecBlocks_(4), + totalCodewords_(0) { + ecBlocks_[0] = ecBlocks1; + ecBlocks_[1] = ecBlocks2; + ecBlocks_[2] = ecBlocks3; + ecBlocks_[3] = ecBlocks4; + + int total = 0; + int ecCodewords = ecBlocks1->getECCodewords(); + vector &ecbArray = ecBlocks1->getECBlocks(); + for (size_t i = 0; i < ecbArray.size(); i++) { + ECB *ecBlock = ecbArray[i]; + total += ecBlock->getCount() * (ecBlock->getDataCodewords() + ecCodewords); + } + totalCodewords_ = total; +} + +Version::~Version() { + delete &alignmentPatternCenters_; + for (size_t i = 0; i < ecBlocks_.size(); i++) { + delete ecBlocks_[i]; + } +} + +Version *Version::decodeVersionInformation(unsigned int versionBits) { + int bestDifference = numeric_limits::max(); + size_t bestVersion = 0; + ErrorHandler err_handler; + for (int i = 0; i < N_VERSION_DECODE_INFOS; i++) { + unsigned targetVersion = VERSION_DECODE_INFO[i]; + // Do the version info bits match exactly? done. + if (targetVersion == versionBits) { + // return getVersionForNumber(i + 7 ); + Version *version = getVersionForNumber(i + 7, err_handler); + if (err_handler.ErrCode()) return 0; + return version; + } + // 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 4 bits. + if (bestDifference <= 3) { + // return getVersionForNumber(bestVersion); + Version *version = getVersionForNumber(bestVersion, err_handler); + if (err_handler.ErrCode()) return 0; + return version; + } + // If we didn't find a close enough match, fail + return 0; +} + +Ref Version::buildFixedPatternValue(ErrorHandler &err_handler) { + int dimension = getDimensionForVersion(err_handler); + if (err_handler.ErrCode()) return Ref(); + + Ref fixedInfo(new BitMatrix(dimension, err_handler)); + if (err_handler.ErrCode()) return Ref(); + + // first timming patterns + for (int i = 0; i < dimension; i += 2) fixedInfo->set(i, 6); + for (int i = 0; i < dimension; i += 2) fixedInfo->set(6, i); + + // FP top left + fixedInfo->setRegion(0, 0, 8, 8, err_handler); + fixedInfo->flipRegion(0, 0, 8, 8, err_handler); + fixedInfo->flipRegion(0, 0, 7, 7, err_handler); + fixedInfo->flipRegion(1, 1, 5, 5, err_handler); + fixedInfo->flipRegion(2, 2, 3, 3, err_handler); + + // FP top right + fixedInfo->setRegion(dimension - 8, 0, 8, 8, err_handler); + fixedInfo->flipRegion(dimension - 8, 0, 8, 8, err_handler); + fixedInfo->flipRegion(dimension - 7, 0, 7, 7, err_handler); + fixedInfo->flipRegion(dimension - 6, 1, 5, 5, err_handler); + fixedInfo->flipRegion(dimension - 5, 2, 3, 3, err_handler); + + // FP bottom left + fixedInfo->setRegion(0, dimension - 8, 8, 8, err_handler); + fixedInfo->flipRegion(0, dimension - 8, 8, 8, err_handler); + fixedInfo->flipRegion(0, dimension - 7, 7, 7, err_handler); + fixedInfo->flipRegion(1, dimension - 6, 5, 5, err_handler); + fixedInfo->flipRegion(2, dimension - 5, 3, 3, err_handler); + if (err_handler.ErrCode()) return Ref(); + + // Alignment patterns + size_t max = alignmentPatternCenters_.size(); + for (size_t x = 0; x < max; x++) { + int i = alignmentPatternCenters_[x] - 2; + for (size_t y = 0; y < max; y++) { + if ((x == 0 && (y == 0 || y == max - 1)) || (x == max - 1 && y == 0)) { + // No alignment patterns near the three finder patterns + continue; + } + fixedInfo->setRegion(alignmentPatternCenters_[y] - 2, i, 5, 5, err_handler); + // fixedInfo->flipRegion(alignmentPatternCenters_[y] - 2, i, 5, 5); + fixedInfo->flipRegion(alignmentPatternCenters_[y] - 1, i + 1, 3, 3, err_handler); + fixedInfo->flipRegion(alignmentPatternCenters_[y], i + 2, 1, 1, err_handler); + if (err_handler.ErrCode()) return Ref(); + } + } + return fixedInfo; +} + +Ref Version::buildFixedPatternTemplate(ErrorHandler &err_handler) { + int dimension = getDimensionForVersion(err_handler); + Ref functionPattern(new BitMatrix(dimension, err_handler)); + if (err_handler.ErrCode()) return Ref(); + + // Top left finder pattern + separator + format + functionPattern->setRegion(0, 0, 8, 8, err_handler); + // Top right finder pattern + separator + format + functionPattern->setRegion(dimension - 8, 0, 8, 8, err_handler); + // Bottom left finder pattern + separator + format + functionPattern->setRegion(0, dimension - 8, 8, 8, err_handler); + if (err_handler.ErrCode()) return Ref(); + + // Alignment patterns + size_t max = alignmentPatternCenters_.size(); + for (size_t x = 0; x < max; x++) { + int i = alignmentPatternCenters_[x] - 2; + for (size_t y = 0; y < max; y++) { + if ((x == 0 && (y == 0 || y == max - 1)) || (x == max - 1 && y == 0)) { + // No alignment patterns near the three finder patterns + continue; + } + functionPattern->setRegion(alignmentPatternCenters_[y] - 2, i, 5, 5, err_handler); + } + } + + // Vertical timing pattern + functionPattern->setRegion(6, 8, 1, dimension - 16, err_handler); + // Horizontal timing pattern + functionPattern->setRegion(8, 6, dimension - 16, 1, err_handler); + if (err_handler.ErrCode()) return Ref(); + + return functionPattern; +} + +Ref Version::buildFunctionPattern(ErrorHandler &err_handler) { + int dimension = getDimensionForVersion(err_handler); + Ref functionPattern(new BitMatrix(dimension, err_handler)); + if (err_handler.ErrCode()) return Ref(); + + // Top left finder pattern + separator + format + functionPattern->setRegion(0, 0, 9, 9, err_handler); + // Top right finder pattern + separator + format + functionPattern->setRegion(dimension - 8, 0, 8, 9, err_handler); + // Bottom left finder pattern + separator + format + functionPattern->setRegion(0, dimension - 8, 9, 8, err_handler); + + // Alignment patterns + size_t max = alignmentPatternCenters_.size(); + for (size_t x = 0; x < max; x++) { + int i = alignmentPatternCenters_[x] - 2; + for (size_t y = 0; y < max; y++) { + if ((x == 0 && (y == 0 || y == max - 1)) || (x == max - 1 && y == 0)) { + // No alignment patterns near the three finder patterns + continue; + } + functionPattern->setRegion(alignmentPatternCenters_[y] - 2, i, 5, 5, err_handler); + } + } + + // Vertical timing pattern + functionPattern->setRegion(6, 9, 1, dimension - 17, err_handler); + // Horizontal timing pattern + functionPattern->setRegion(9, 6, dimension - 17, 1, err_handler); + if (err_handler.ErrCode()) return Ref(); + + if (versionNumber_ > 6) { + // Version info, top right + functionPattern->setRegion(dimension - 11, 0, 3, 6, err_handler); + // Version info, bottom left + functionPattern->setRegion(0, dimension - 11, 6, 3, err_handler); + if (err_handler.ErrCode()) return Ref(); + } + + return functionPattern; +} + +int Version::buildVersions() { + VERSIONS.push_back(Ref(new Version( + 1, intArray(0), 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))))); + VERSIONS.push_back(Ref(new Version( + 2, intArray(2, 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))))); + VERSIONS.push_back(Ref(new Version( + 3, intArray(2, 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))))); + VERSIONS.push_back(Ref(new Version( + 4, intArray(2, 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))))); + VERSIONS.push_back(Ref(new Version( + 5, intArray(2, 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))))); + VERSIONS.push_back(Ref(new Version( + 6, intArray(2, 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))))); + VERSIONS.push_back(Ref(new Version( + 7, intArray(3, 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))))); + VERSIONS.push_back( + Ref(new Version(8, intArray(3, 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))))); + VERSIONS.push_back( + Ref(new Version(9, intArray(3, 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))))); + VERSIONS.push_back(Ref(new Version(10, intArray(3, 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))))); + VERSIONS.push_back( + Ref(new Version(11, intArray(3, 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))))); + VERSIONS.push_back(Ref(new Version(12, intArray(3, 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))))); + VERSIONS.push_back( + Ref(new Version(13, intArray(3, 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))))); + VERSIONS.push_back(Ref(new Version( + 14, intArray(4, 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))))); + VERSIONS.push_back(Ref(new Version( + 15, intArray(4, 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))))); + VERSIONS.push_back(Ref(new Version( + 16, intArray(4, 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))))); + VERSIONS.push_back(Ref(new Version( + 17, intArray(4, 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))))); + VERSIONS.push_back(Ref(new Version( + 18, intArray(4, 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))))); + VERSIONS.push_back(Ref(new Version( + 19, intArray(4, 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))))); + VERSIONS.push_back(Ref(new Version( + 20, intArray(4, 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))))); + VERSIONS.push_back(Ref(new Version( + 21, intArray(5, 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))))); + VERSIONS.push_back(Ref(new Version( + 22, intArray(5, 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))))); + VERSIONS.push_back(Ref(new Version( + 23, intArray(5, 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))))); + VERSIONS.push_back(Ref(new Version( + 24, intArray(5, 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))))); + VERSIONS.push_back(Ref(new Version( + 25, intArray(5, 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))))); + VERSIONS.push_back(Ref(new Version( + 26, intArray(5, 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))))); + VERSIONS.push_back(Ref(new Version( + 27, intArray(5, 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))))); + VERSIONS.push_back( + Ref(new Version(28, intArray(6, 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))))); + VERSIONS.push_back( + Ref(new Version(29, intArray(6, 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))))); + VERSIONS.push_back( + Ref(new Version(30, intArray(6, 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))))); + VERSIONS.push_back( + Ref(new Version(31, intArray(6, 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))))); + VERSIONS.push_back(Ref( + new Version(32, intArray(6, 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))))); + VERSIONS.push_back( + Ref(new Version(33, intArray(6, 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))))); + VERSIONS.push_back( + Ref(new Version(34, intArray(6, 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))))); + VERSIONS.push_back( + Ref(new Version(35, intArray(7, 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))))); + VERSIONS.push_back( + Ref(new Version(36, intArray(7, 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))))); + VERSIONS.push_back( + Ref(new Version(37, intArray(7, 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))))); + VERSIONS.push_back( + Ref(new Version(38, intArray(7, 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))))); + VERSIONS.push_back( + Ref(new Version(39, intArray(7, 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))))); + VERSIONS.push_back( + Ref(new Version(40, intArray(7, 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))))); + return VERSIONS.size(); +} + +} // namespace qrcode +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/qrcode/version.hpp b/modules/wechat_qrcode/src/zxing/qrcode/version.hpp new file mode 100644 index 0000000..68bb767 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/qrcode/version.hpp @@ -0,0 +1,86 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_QRCODE_VERSION_HPP__ +#define __ZXING_QRCODE_VERSION_HPP__ + +#include "../common/bitmatrix.hpp" +#include "../common/counted.hpp" +#include "../errorhandler.hpp" +#include "error_correction_level.hpp" + +namespace zxing { +namespace qrcode { + +// Encapsualtes 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. +class ECB { +private: + int count_; + int dataCodewords_; + +public: + ECB(int count, int dataCodewords); + int getCount(); + int getDataCodewords(); +}; + +// 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.

+class ECBlocks { +private: + int ecCodewords_; + std::vector ecBlocks_; + +public: + ECBlocks(int ecCodewords, ECB *ecBlocks); + ECBlocks(int ecCodewords, ECB *ecBlocks1, ECB *ecBlocks2); + int getECCodewords(); + std::vector &getECBlocks(); + ~ECBlocks(); +}; + +class Version : public Counted { +private: + int versionNumber_; + std::vector &alignmentPatternCenters_; + std::vector ecBlocks_; + int totalCodewords_; + Version(int versionNumber, std::vector *alignmentPatternCenters, ECBlocks *ecBlocks1, + ECBlocks *ecBlocks2, ECBlocks *ecBlocks3, ECBlocks *ecBlocks4); + +public: + static unsigned int VERSION_DECODE_INFO[]; + static int N_VERSION_DECODE_INFOS; + static std::vector > VERSIONS; + + ~Version(); + int getVersionNumber(); + std::vector &getAlignmentPatternCenters(); + int getTotalCodewords(); + int getDimensionForVersion(ErrorHandler &err_handler); + ECBlocks &getECBlocksForLevel(ErrorCorrectionLevel &ecLevel); + static Version *getProvisionalVersionForDimension(int dimension, ErrorHandler &err_handler); + static Version *getVersionForNumber(int versionNumber, ErrorHandler &err_handler); + static Version *decodeVersionInformation(unsigned int versionBits); + Ref buildFunctionPattern(ErrorHandler &err_handler); + Ref buildFixedPatternValue(ErrorHandler &err_handler); + Ref buildFixedPatternTemplate(ErrorHandler &err_handler); + static int buildVersions(); +}; +} // namespace qrcode +} // namespace zxing + +#endif // __ZXING_QRCODE_VERSION_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/reader.cpp b/modules/wechat_qrcode/src/zxing/reader.cpp new file mode 100644 index 0000000..da54ccb --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/reader.cpp @@ -0,0 +1,28 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../precomp.hpp" +#include "reader.hpp" + +namespace zxing { + +Reader::~Reader() {} + +Ref Reader::decode(Ref image) { return decode(image, DecodeHints()); } + +unsigned int Reader::getDecodeID() { return 0; } + +void Reader::setDecodeID(unsigned int) {} + +float Reader::getPossibleFix() { return 0.0; } + + +string Reader::name() { return "unknow"; } + +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/reader.hpp b/modules/wechat_qrcode/src/zxing/reader.hpp new file mode 100644 index 0000000..cb3e7ea --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/reader.hpp @@ -0,0 +1,39 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_READER_HPP__ +#define __ZXING_READER_HPP__ + +#include "binarybitmap.hpp" +#include "decodehints.hpp" +#include "errorhandler.hpp" +#include "result.hpp" + +namespace zxing { + +class Reader : public Counted { +protected: + Reader() {} + +public: + virtual Ref decode(Ref image); + virtual Ref decode(Ref image, DecodeHints hints) = 0; + + virtual ~Reader(); + virtual string name(); + virtual unsigned int getDecodeID(); + virtual void setDecodeID(unsigned int id); + + virtual float getPossibleFix(); +}; + +} // namespace zxing + +#endif // __ZXING_READER_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/result.cpp b/modules/wechat_qrcode/src/zxing/result.cpp new file mode 100644 index 0000000..e33c2c5 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/result.cpp @@ -0,0 +1,71 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../precomp.hpp" +#include "result.hpp" + +using zxing::ArrayRef; +using zxing::Ref; +using zxing::Result; +using zxing::ResultPoint; +using zxing::String; + +Result::Result(Ref text, ArrayRef rawBytes, ArrayRef > resultPoints) + : text_(text), rawBytes_(rawBytes), resultPoints_(resultPoints) { + charset_ = "UTF-8"; + + qrcodeVersion_ = -1; + pyramidLv_ = -1; + binaryMethod_ = -1; + ecLevel_ = '0'; +} + +Result::Result(Ref text, ArrayRef rawBytes, ArrayRef > resultPoints, + std::string charset) + : text_(text), rawBytes_(rawBytes), resultPoints_(resultPoints), charset_(charset) { + qrcodeVersion_ = -1; + pyramidLv_ = -1; + binaryMethod_ = -1; + ecLevel_ = '0'; +} + +Result::Result(Ref text, ArrayRef rawBytes, ArrayRef > resultPoints, + std::string charset, int QRCodeVersion, string ecLevel, string charsetMode) + : text_(text), + rawBytes_(rawBytes), + resultPoints_(resultPoints), + charset_(charset), + qrcodeVersion_(QRCodeVersion), + ecLevel_(ecLevel), + charsetMode_(charsetMode) { + pyramidLv_ = -1; + binaryMethod_ = -1; +} + +Result::~Result() {} + +Ref Result::getText() { return text_; } + +ArrayRef Result::getRawBytes() { return rawBytes_; } + +ArrayRef > const& Result::getResultPoints() const { return resultPoints_; } + +ArrayRef >& Result::getResultPoints() { return resultPoints_; } + +void Result::enlargeResultPoints(int scale) { + for (int i = 0; i < resultPoints_->size(); i++) { + resultPoints_[i] = Ref(new ResultPoint( + resultPoints_[i]->getX() * (float)scale, resultPoints_[i]->getY() * (float)scale)); + } + return; +} + +std::string Result::getCharset() const { return charset_; } + +std::string zxing::Result::getChartsetMode() const { return charsetMode_; } diff --git a/modules/wechat_qrcode/src/zxing/result.hpp b/modules/wechat_qrcode/src/zxing/result.hpp new file mode 100644 index 0000000..6bf053c --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/result.hpp @@ -0,0 +1,78 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_RESULT_HPP__ +#define __ZXING_RESULT_HPP__ + +#include +#include "common/array.hpp" +#include "common/counted.hpp" +#include "common/str.hpp" +#include "resultpoint.hpp" + +#include + +namespace zxing { + +class Result : public Counted { +private: + Ref text_; + ArrayRef rawBytes_; + ArrayRef > resultPoints_; + std::string charset_; + int qrcodeVersion_; + int pyramidLv_; + int binaryMethod_; + string ecLevel_; + string charsetMode_; + string scale_list_; + float decode_scale_; + uint32_t detect_time_; + uint32_t sr_time_; + +public: + Result(Ref text, ArrayRef rawBytes, ArrayRef > resultPoints); + + Result(Ref text, ArrayRef rawBytes, ArrayRef > resultPoints, + std::string charset); + + Result(Ref text, ArrayRef rawBytes, ArrayRef > resultPoints, + std::string charset, int QRCodeVersion, string ecLevel, string charsetMode); + + ~Result(); + + Ref getText(); + ArrayRef getRawBytes(); + ArrayRef > const& getResultPoints() const; + ArrayRef >& getResultPoints(); + std::string getCharset() const; + std::string getChartsetMode() const; + void enlargeResultPoints(int scale); + + int getQRCodeVersion() const { return qrcodeVersion_; }; + void setQRCodeVersion(int QRCodeVersion) { qrcodeVersion_ = QRCodeVersion; }; + int getPyramidLv() const { return pyramidLv_; }; + void setPyramidLv(int pyramidLv) { pyramidLv_ = pyramidLv; }; + int getBinaryMethod() const { return binaryMethod_; }; + void setBinaryMethod(int binaryMethod) { binaryMethod_ = binaryMethod; }; + string getEcLevel() const { return ecLevel_; } + void setEcLevel(char ecLevel) { ecLevel_ = ecLevel; } + std::string getScaleList() { return scale_list_; }; + void setScaleList(const std::string& scale_list) { scale_list_ = scale_list; }; + float getDecodeScale() { return decode_scale_; }; + void setDecodeScale(float decode_scale) { decode_scale_ = decode_scale; }; + uint32_t getDetectTime() { return detect_time_; }; + void setDetectTime(uint32_t detect_time) { detect_time_ = detect_time; }; + uint32_t getSrTime() { return sr_time_; }; + void setSrTime(uint32_t sr_time) { sr_time_ = sr_time; }; +}; + +} // namespace zxing +#endif // __ZXING_RESULT_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/resultpoint.cpp b/modules/wechat_qrcode/src/zxing/resultpoint.cpp new file mode 100644 index 0000000..0fab518 --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/resultpoint.cpp @@ -0,0 +1,101 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). +#include "../precomp.hpp" +#include "resultpoint.hpp" +#include "common/mathutils.hpp" + +using zxing::common::MathUtils; + +namespace zxing { + +ResultPoint::ResultPoint() : posX_(0), posY_(0) {} + +ResultPoint::ResultPoint(float x, float y) : posX_(x), posY_(y) {} + +ResultPoint::ResultPoint(int x, int y) : posX_(float(x)), posY_(float(y)) {} + +ResultPoint::~ResultPoint() {} + +float ResultPoint::getX() const { return posX_; } + +float ResultPoint::getY() const { return posY_; } + +void ResultPoint::SetX(float fX) { posX_ = fX; } + +void ResultPoint::SetY(float fY) { posY_ = fY; } + +bool ResultPoint::equals(Ref other) { + return (fabs(posX_ - other->getX()) <= 1e-6) && (fabs(posY_ - other->getY()) <= 1e-6); +} + +/** + *

Orders an array of three ResultPoints in an order [A,B,C] such that AB < + * AC and BC < AC and the angle between BC and BA is less than 180 degrees. + */ +void ResultPoint::orderBestPatterns(std::vector > &patterns) { + // Find distances between pattern centers + float zeroOneDistance = distance(patterns[0]->getX(), patterns[1]->getX(), patterns[0]->getY(), + patterns[1]->getY()); + float oneTwoDistance = distance(patterns[1]->getX(), patterns[2]->getX(), patterns[1]->getY(), + patterns[2]->getY()); + float zeroTwoDistance = distance(patterns[0]->getX(), patterns[2]->getX(), patterns[0]->getY(), + patterns[2]->getY()); + + Ref pointA, pointB, 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) { + Ref temp = pointA; + pointA = pointC; + pointC = temp; + } + + patterns[0] = pointA; + patterns[1] = pointB; + patterns[2] = pointC; +} + +float ResultPoint::distance(Ref pattern1, Ref pattern2) { + return MathUtils::distance(pattern1->posX_, pattern1->posY_, pattern2->posX_, pattern2->posY_); +} + +float ResultPoint::distance(float x1, float x2, float y1, float y2) { + float xDiff = x1 - x2; + float yDiff = y1 - y2; + return (float)sqrt((double)(xDiff * xDiff + yDiff * yDiff)); +} + +float ResultPoint::crossProductZ(Ref pointA, Ref pointB, + Ref pointC) { + float bX = pointB->getX(); + float bY = pointB->getY(); + return ((pointC->getX() - bX) * (pointA->getY() - bY)) - + ((pointC->getY() - bY) * (pointA->getX() - bX)); +} +} // namespace zxing diff --git a/modules/wechat_qrcode/src/zxing/resultpoint.hpp b/modules/wechat_qrcode/src/zxing/resultpoint.hpp new file mode 100644 index 0000000..cb5d05d --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/resultpoint.hpp @@ -0,0 +1,47 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_RESULTPOINT_HPP__ +#define __ZXING_RESULTPOINT_HPP__ + +#include "common/counted.hpp" + +namespace zxing { + +class ResultPoint : public Counted { +protected: + float posX_; + float posY_; + +public: + ResultPoint(); + ResultPoint(float x, float y); + ResultPoint(int x, int y); + virtual ~ResultPoint(); + + virtual float getX() const; + virtual float getY() const; + virtual void SetX(float fX); + virtual void SetY(float fY); + + bool equals(Ref other); + + static void orderBestPatterns(std::vector > &patterns); + static float distance(Ref point1, Ref point2); + static float distance(float x1, float x2, float y1, float y2); + +private: + static float crossProductZ(Ref pointA, Ref pointB, + Ref pointC); +}; + +} // namespace zxing + +#endif // __ZXING_RESULTPOINT_HPP__ diff --git a/modules/wechat_qrcode/src/zxing/zxing.hpp b/modules/wechat_qrcode/src/zxing/zxing.hpp new file mode 100644 index 0000000..cae7e7d --- /dev/null +++ b/modules/wechat_qrcode/src/zxing/zxing.hpp @@ -0,0 +1,78 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +// +// Modified from ZXing. Copyright ZXing authors. +// Licensed under the Apache License, Version 2.0 (the "License"). + +#ifndef __ZXING_ZXING_HPP__ +#define __ZXING_ZXING_HPP__ + + +#define COUNTER_TYPE short + + +#define ZXING_ARRAY_LEN(v) ((int)(sizeof(v) / sizeof(v[0]))) +#define ZX_LOG_DIGITS(digits) \ + ((digits == 8) \ + ? 3 \ + : ((digits == 16) \ + ? 4 \ + : ((digits == 32) ? 5 : ((digits == 64) ? 6 : ((digits == 128) ? 7 : (-1)))))) + +#ifndef USE_QRCODE_ONLY +#define USE_ONED_WRITER 1 +#endif + +#if defined(__ANDROID_API__) + +#ifndef NO_ICONV +#define NO_ICONV +#endif + +#endif + + + +#ifndef NO_ICONV_INSIDE +#define NO_ICONV_INSIDE +#endif + +#define ZXING_MAX_WIDTH 2048 +#define ZXING_MAX_HEIGHT 2048 + +namespace zxing { +typedef char byte; +typedef unsigned char boolean; +// typedef unsigned short ushort; +} // namespace zxing + +#include + +#if defined(_MSC_VER) + +#ifndef NO_ICONV +#define NO_ICONV +#endif + +#endif + +#include + +namespace zxing { +inline bool isnan(float v) { return std::isnan(v); } +inline bool isnan(double v) { return std::isnan(v); } +inline float nan() { return std::numeric_limits::quiet_NaN(); } +} // namespace zxing + +#ifndef ZXING_TIME +#define ZXING_TIME(string) (void)0 +#endif +#ifndef ZXING_TIME_MARK +#define ZXING_TIME_MARK(string) (void)0 +#endif + +#endif // __ZXING_ZXING_HPP__ diff --git a/modules/wechat_qrcode/test/test_main.cpp b/modules/wechat_qrcode/test/test_main.cpp new file mode 100644 index 0000000..47f2eba --- /dev/null +++ b/modules/wechat_qrcode/test/test_main.cpp @@ -0,0 +1,14 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#include "test_precomp.hpp" + +#if defined(HAVE_HPX) +#include +#endif + +CV_TEST_MAIN("cv") diff --git a/modules/wechat_qrcode/test/test_precomp.hpp b/modules/wechat_qrcode/test/test_precomp.hpp new file mode 100644 index 0000000..0f5ee5b --- /dev/null +++ b/modules/wechat_qrcode/test/test_precomp.hpp @@ -0,0 +1,14 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#ifndef __OPENCV_TEST_PRECOMP_HPP__ +#define __OPENCV_TEST_PRECOMP_HPP__ + +#include "opencv2/ts.hpp" +#include "opencv2/wechat_qrcode.hpp" + +#endif diff --git a/modules/wechat_qrcode/test/test_qrcode.cpp b/modules/wechat_qrcode/test/test_qrcode.cpp new file mode 100644 index 0000000..5de6533 --- /dev/null +++ b/modules/wechat_qrcode/test/test_qrcode.cpp @@ -0,0 +1,293 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Tencent is pleased to support the open source community by making WeChat QRCode available. +// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + +#include "test_precomp.hpp" + +namespace opencv_test { +namespace { +std::string qrcode_images_name[] = { + "version_1_down.jpg", /*"version_1_left.jpg", "version_1_right.jpg", "version_1_up.jpg",*/ + "version_1_top.jpg", + /*"version_2_down.jpg",*/ "version_2_left.jpg", /*"version_2_right.jpg",*/ + "version_2_up.jpg", + "version_2_top.jpg", + "version_3_down.jpg", + "version_3_left.jpg", + /*"version_3_right.jpg",*/ "version_3_up.jpg", + "version_3_top.jpg", + "version_4_down.jpg", + "version_4_left.jpg", + /*"version_4_right.jpg",*/ "version_4_up.jpg", + "version_4_top.jpg", + "version_5_down.jpg", + "version_5_left.jpg", + /*"version_5_right.jpg",*/ "version_5_up.jpg", + "version_5_top.jpg", + "russian.jpg", + "kanji.jpg", /*"link_github_ocv.jpg",*/ + "link_ocv.jpg", + "link_wiki_cv.jpg"}; + +std::string qrcode_images_close[] = {/*"close_1.png",*/ "close_2.png", "close_3.png", "close_4.png", + "close_5.png"}; +std::string qrcode_images_monitor[] = {"monitor_1.png", "monitor_2.png", "monitor_3.png", + "monitor_4.png", "monitor_5.png"}; +std::string qrcode_images_curved[] = {"curved_1.jpg", /*"curved_2.jpg", "curved_3.jpg", + "curved_4.jpg",*/ + "curved_5.jpg", "curved_6.jpg", + /*"curved_7.jpg", "curved_8.jpg"*/}; +// std::string qrcode_images_multiple[] = {"2_qrcodes.png", "3_close_qrcodes.png", "3_qrcodes.png", +// "4_qrcodes.png", "5_qrcodes.png", "6_qrcodes.png", +// "7_qrcodes.png", "8_close_qrcodes.png"}; + +typedef testing::TestWithParam Objdetect_QRCode; +TEST_P(Objdetect_QRCode, regression) { + const std::string name_current_image = GetParam(); + const std::string root = "qrcode/"; + + std::string image_path = findDataFile(root + name_current_image); + Mat src = imread(image_path, IMREAD_GRAYSCALE); + ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; + + vector points; + // can not find the model file + // so we temporarily comment it out + // auto detector = wechat_qrcode::WeChatQRCode( + // findDataFile("detect.prototxt", false), findDataFile("detect.caffemodel", false), + // findDataFile("sr.prototxt", false), findDataFile("sr.caffemodel", false)); + auto detector = wechat_qrcode::WeChatQRCode(); + auto decoded_info = detector.detectAndDecode(src, points); + + const std::string dataset_config = findDataFile(root + "dataset_config.json"); + FileStorage file_config(dataset_config, FileStorage::READ); + ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config; + { + FileNode images_list = file_config["test_images"]; + size_t images_count = static_cast(images_list.size()); + ASSERT_GT(images_count, 0u) + << "Can't find validation data entries in 'test_images': " << dataset_config; + + for (size_t index = 0; index < images_count; index++) { + FileNode config = images_list[(int)index]; + std::string name_test_image = config["image_name"]; + if (name_test_image == name_current_image) { + std::string original_info = config["info"]; + string decoded_str; + if (decoded_info.size()) { + decoded_str = decoded_info[0]; + } + EXPECT_EQ(decoded_str, original_info); + return; // done + } + } + std::cerr << "Not found results for '" << name_current_image + << "' image in config file:" << dataset_config << std::endl + << "Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data." + << std::endl; + } +} + +typedef testing::TestWithParam Objdetect_QRCode_Close; +TEST_P(Objdetect_QRCode_Close, regression) { + const std::string name_current_image = GetParam(); + const std::string root = "qrcode/close/"; + + std::string image_path = findDataFile(root + name_current_image); + Mat src = imread(image_path, IMREAD_GRAYSCALE); + ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; + + vector points; + // can not find the model file + // so we temporarily comment it out + // auto detector = wechat_qrcode::WeChatQRCode( + // findDataFile("detect.prototxt", false), findDataFile("detect.caffemodel", false), + // findDataFile("sr.prototxt", false), findDataFile("sr.caffemodel", false)); + auto detector = wechat_qrcode::WeChatQRCode(); + auto decoded_info = detector.detectAndDecode(src, points); + + const std::string dataset_config = findDataFile(root + "dataset_config.json"); + FileStorage file_config(dataset_config, FileStorage::READ); + ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config; + { + FileNode images_list = file_config["close_images"]; + size_t images_count = static_cast(images_list.size()); + ASSERT_GT(images_count, 0u) + << "Can't find validation data entries in 'close_images': " << dataset_config; + + for (size_t index = 0; index < images_count; index++) { + FileNode config = images_list[(int)index]; + std::string name_test_image = config["image_name"]; + if (name_test_image == name_current_image) { + std::string original_info = config["info"]; + string decoded_str; + if (decoded_info.size()) { + decoded_str = decoded_info[0]; + } + EXPECT_EQ(decoded_str, original_info); + return; // done + } + } + std::cerr << "Not found results for '" << name_current_image + << "' image in config file:" << dataset_config << std::endl + << "Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data." + << std::endl; + } +} + +typedef testing::TestWithParam Objdetect_QRCode_Monitor; +TEST_P(Objdetect_QRCode_Monitor, regression) { + const std::string name_current_image = GetParam(); + const std::string root = "qrcode/monitor/"; + + std::string image_path = findDataFile(root + name_current_image); + Mat src = imread(image_path, IMREAD_GRAYSCALE); + ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; + + vector points; + // can not find the model file + // so we temporarily comment it out + // auto detector = wechat_qrcode::WeChatQRCode( + // findDataFile("detect.prototxt", false), findDataFile("detect.caffemodel", false), + // findDataFile("sr.prototxt", false), findDataFile("sr.caffemodel", false)); + auto detector = wechat_qrcode::WeChatQRCode(); + auto decoded_info = detector.detectAndDecode(src, points); + + const std::string dataset_config = findDataFile(root + "dataset_config.json"); + FileStorage file_config(dataset_config, FileStorage::READ); + ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config; + { + FileNode images_list = file_config["monitor_images"]; + size_t images_count = static_cast(images_list.size()); + ASSERT_GT(images_count, 0u) + << "Can't find validation data entries in 'monitor_images': " << dataset_config; + + for (size_t index = 0; index < images_count; index++) { + FileNode config = images_list[(int)index]; + std::string name_test_image = config["image_name"]; + if (name_test_image == name_current_image) { + std::string original_info = config["info"]; + string decoded_str; + if (decoded_info.size()) { + decoded_str = decoded_info[0]; + } + EXPECT_EQ(decoded_str, original_info); + return; // done + } + } + std::cerr << "Not found results for '" << name_current_image + << "' image in config file:" << dataset_config << std::endl + << "Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data." + << std::endl; + } +} + +typedef testing::TestWithParam Objdetect_QRCode_Curved; +TEST_P(Objdetect_QRCode_Curved, regression) { + const std::string name_current_image = GetParam(); + const std::string root = "qrcode/curved/"; + + std::string image_path = findDataFile(root + name_current_image); + Mat src = imread(image_path, IMREAD_GRAYSCALE); + ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; + + vector points; + // can not find the model file + // so we temporarily comment it out + // auto detector = wechat_qrcode::WeChatQRCode( + // findDataFile("detect.prototxt", false), findDataFile("detect.caffemodel", false), + // findDataFile("sr.prototxt", false), findDataFile("sr.caffemodel", false)); + auto detector = wechat_qrcode::WeChatQRCode(); + auto decoded_info = detector.detectAndDecode(src, points); + + const std::string dataset_config = findDataFile(root + "dataset_config.json"); + FileStorage file_config(dataset_config, FileStorage::READ); + ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config; + { + FileNode images_list = file_config["test_images"]; + size_t images_count = static_cast(images_list.size()); + ASSERT_GT(images_count, 0u) + << "Can't find validation data entries in 'test_images': " << dataset_config; + + for (size_t index = 0; index < images_count; index++) { + FileNode config = images_list[(int)index]; + std::string name_test_image = config["image_name"]; + if (name_test_image == name_current_image) { + std::string original_info = config["info"]; + string decoded_str; + if (decoded_info.size()) { + decoded_str = decoded_info[0]; + } + EXPECT_EQ(decoded_str, original_info); + return; // done + } + } + std::cerr << "Not found results for '" << name_current_image + << "' image in config file:" << dataset_config << std::endl + << "Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data." + << std::endl; + } +} + +typedef testing::TestWithParam Objdetect_QRCode_Multi; +TEST_P(Objdetect_QRCode_Multi, regression) { + const std::string name_current_image = GetParam(); + const std::string root = "qrcode/multiple/"; + + std::string image_path = findDataFile(root + name_current_image); + Mat src = imread(image_path); + ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; + + vector points; + // can not find the model file + // so we temporarily comment it out + // auto detector = wechat_qrcode::WeChatQRCode( + // findDataFile("detect.prototxt", false), findDataFile("detect.caffemodel", false), + // findDataFile("sr.prototxt", false), findDataFile("sr.caffemodel", false)); + auto detector = wechat_qrcode::WeChatQRCode(); + vector decoded_info = detector.detectAndDecode(src, points); + + const std::string dataset_config = findDataFile(root + "dataset_config.json"); + FileStorage file_config(dataset_config, FileStorage::READ); + ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config; + { + FileNode images_list = file_config["multiple_images"]; + size_t images_count = static_cast(images_list.size()); + ASSERT_GT(images_count, 0u) + << "Can't find validation data entries in 'test_images': " << dataset_config; + for (size_t index = 0; index < images_count; index++) { + FileNode config = images_list[(int)index]; + std::string name_test_image = config["image_name"]; + if (name_test_image == name_current_image) { + size_t count_eq_info = 0; + for (int i = 0; i < int(decoded_info.size()); i++) { + for (int j = 0; j < int(config["info"].size()); j++) { + std::string original_info = config["info"][j]; + if (original_info == decoded_info[i]) { + count_eq_info++; + break; + } + } + } + EXPECT_EQ(config["info"].size(), count_eq_info); + return; // done + } + } + std::cerr << "Not found results for '" << name_current_image + << "' image in config file:" << dataset_config << std::endl + << "Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data." + << std::endl; + } +} + +INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode, testing::ValuesIn(qrcode_images_name)); +INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Close, testing::ValuesIn(qrcode_images_close)); +INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Monitor, testing::ValuesIn(qrcode_images_monitor)); +INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Curved, testing::ValuesIn(qrcode_images_curved)); +// INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Multi, testing::ValuesIn(qrcode_images_multiple)); + +} // namespace +} // namespace opencv_test