├── .gitignore
├── .idea
├── caches
│ └── build_file_checksums.ser
├── codeStyles
│ └── Project.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── .project
├── .settings
└── org.eclipse.buildship.core.prefs
├── LICENSE
├── README-zh_CN.md
├── README.md
├── art
├── apache_feather.svg
├── awesome-qr-1.png
├── awesome-qr-2.png
├── awesome-qr-3.png
├── awesome-qr-4.png
├── awesome-qr-5.png
├── awesome-qr-6.png
├── banner.png
├── banner2.jpg
├── banner_new.png
├── banner_v3.png
├── gif.gif
├── no_logo.png
├── play_store_badge.png
└── with_logo.png
├── awesomeqrcode-gif-stub
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── github
│ │ └── sumimakito
│ │ └── awesomeqrsample
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── github
│ │ └── sumimakito
│ │ └── awesomeqr
│ │ ├── GifPipeline.kt
│ │ └── option
│ │ └── background
│ │ ├── Background.kt
│ │ └── GifBackground.kt
│ └── test
│ └── java
│ └── com
│ └── github
│ └── sumimakito
│ └── awesomeqrsample
│ └── ExampleUnitTest.java
├── awesomeqrcode-gif
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── github
│ │ └── sumimakito
│ │ └── awesomeqrsample
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── github
│ │ └── sumimakito
│ │ └── awesomeqr
│ │ ├── GifPipeline.kt
│ │ └── option
│ │ └── background
│ │ └── GifBackground.kt
│ └── test
│ └── java
│ └── com
│ └── github
│ └── sumimakito
│ └── awesomeqrsample
│ └── ExampleUnitTest.java
├── awesomeqrcode
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── github
│ │ └── sumimakito
│ │ └── awesomeqrsample
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── github
│ │ └── sumimakito
│ │ └── awesomeqr
│ │ ├── AwesomeQrRenderer.kt
│ │ ├── RenderResult.kt
│ │ ├── option
│ │ ├── RenderOption.kt
│ │ ├── background
│ │ │ ├── Background.kt
│ │ │ ├── BlendBackground.kt
│ │ │ └── StillBackground.kt
│ │ ├── color
│ │ │ └── Color.kt
│ │ └── logo
│ │ │ └── Logo.kt
│ │ └── util
│ │ └── RectUtils.kt
│ └── test
│ └── java
│ └── com
│ └── github
│ └── sumimakito
│ └── awesomeqrsample
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | *.apk
11 | /base
12 | /apk9
13 | /instant-app
14 | /release
15 | /expansionpanels
16 | /main.zip
17 | README.orig_md
18 |
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | AwesomeQR
4 | Project AwesomeQR created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | #Thu Mar 22 23:28:46 CST 2018
2 | connection.project.dir=
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/README-zh_CN.md:
--------------------------------------------------------------------------------
1 | > 这篇文档中的内容已经过时,请暂以英文文档的内容为准,谢谢。
2 |
3 |
4 |
5 |
6 |
7 | [](https://jitpack.io/#SumiMakito/AwesomeQRCode)
8 | [](https://github.com/SumiMakito/AwesomeQRCode/releases/latest)
9 | [](https://github.com/SumiMakito/AwesomeQRCode/blob/master/LICENSE)
10 | 
11 |
12 | AwesomeQRCode - 一个优雅的(不起眼的) QR 二维码生成器
13 |
14 | > [Swithc to English Version?](README.md)
15 |
16 | ## 好耶! 演示应用!
17 |
18 |
19 |
20 | ## 样例
21 |
22 | > 拿起你的手机扫描下面的二维码试试吧!
23 |
24 | 样例 1 | 样例 2 | 样例 3
25 | ------------ | ------------- | -------------
26 |
|
|
27 |
28 | 使用圆点做数据点 | 二值化处理 | 带有 Logo
29 | ------------ | ------------- | -------------
30 |
|
|
31 |
32 | ## 引用
33 |
34 | > 万事开头难, 补全就好啦!
35 |
36 | 在项目根目录下的 build.gradle 中补充以下内容,以添加依赖项:
37 |
38 | ```
39 | allprojects {
40 | repositories {
41 | ...
42 | maven { url 'https://jitpack.io' }
43 | }
44 | }
45 | ```
46 |
47 | 在应用模块层级下的 build.gradle 中补充以下内容:
48 |
49 | ```
50 | dependencies {
51 | compile 'com.github.SumiMakito:AwesomeQRCode:1.0.6'
52 | }
53 | ```
54 |
55 | ## 快速上手
56 |
57 | ### 1. "人家只想要 Bitmap 嘛"
58 |
59 | > 原来乃只想要 Bitmap 撒... 满足你!!
60 |
61 | 这种情况下,二维码将同步(synchronously)生成,这有可能阻塞 UI 线程,引起应用无响应(ANR)问题。因此建议在非 UI 线程中使用。
62 |
63 | ```java
64 | new Thread() {
65 | @Override
66 | public void run() {
67 | super.run();
68 | Bitmap qrCode = new AwesomeQRCode.Renderer()
69 | .contents("Makito loves Kafuu Chino.")
70 | .size(800).margin(20)
71 | .render();
72 | }.start();
73 | ```
74 |
75 | ### 2. 异步生成二维码并在 ImageView 中显示
76 |
77 | ```java
78 | new AwesomeQRCode.Renderer()
79 | .contents("Makito loves Kafuu Chino.")
80 | .size(800).margin(20)
81 | .renderAsync(new AwesomeQRCode.Callback() {
82 | @Override
83 | public void onRendered(AwesomeQRCode.Renderer renderer, final Bitmap bitmap) {
84 | runOnUiThread(new Runnable() {
85 | @Override
86 | public void run() {
87 | // 提示: 这里使用 runOnUiThread(...) 来规避从非 UI 线程操作 UI 控件时产生的问题。
88 | imageView.setImageBitmap(bitmap);
89 | }
90 | });
91 | }
92 |
93 | @Override
94 | public void onError(AwesomeQRCode.Renderer renderer, Exception e) {
95 | e.printStackTrace();
96 | }
97 | });
98 | ```
99 |
100 | ## 渲染流程
101 |
102 | ```
103 | Bitmap bitmap = new AwesomeQRCode.Renderer() ...
104 | ```
105 |
106 | ### 基本
107 |
108 | #### .contents(String)
109 |
110 | *必需*
111 | 欲编码的内容。
112 | 默认为 *null*。
113 |
114 | #### .size(int)
115 |
116 | *必需*
117 | 尺寸,长宽一致,包含外边距。
118 | 单位是 *px*。
119 | 默认为 *800*。
120 |
121 | #### .margin(int)
122 |
123 | 二维码图像的外边距。
124 | 单位是 *px*。
125 | 默认为 *20*。
126 |
127 | #### .dataDotScale(float)
128 |
129 | 数据点缩小比例。
130 | 默认为 *0.3f*。
131 |
132 | #### .roundedDataDots(boolean)
133 |
134 | 若为 true,数据区域将以圆点绘制。
135 | 默认为 *false*。
136 |
137 | #### .whiteMargin(boolean)
138 |
139 | 若设为 true,背景图外将绘制白色边框。
140 | 默认为 *true*。
141 |
142 | ### 后处理
143 |
144 | #### .binarize(boolean)
145 |
146 | 若为 true,图像将被二值化处理。
147 | 默认为 *false*。
148 |
149 | #### .binarizeThreshold(int)
150 |
151 | 二值化处理的阈值。
152 | 默认为 *128*。
153 |
154 | ### 颜色
155 |
156 | #### .autoColor(boolean)
157 |
158 | 若为 true,背景图(或 GIF 的每一帧)中的主要颜色将作为非空白区域的颜色。
159 | 默认为 *true*。
160 |
161 | #### .colorDark(int)
162 |
163 | 非空白区域的颜色。
164 | 默认为 *Color.BLACK*。
165 |
166 | #### .colorLight(int)
167 |
168 | 空白区域的颜色。
169 | 默认为 *Color.WHITE*。
170 |
171 | ### Background
172 |
173 | #### .background(Bitmap)
174 |
175 | 欲嵌入的背景图,设为 null 以禁用。
176 | 默认为 *null*。
177 |
178 | #### .backgroundGif(File)
179 |
180 | *必须是 GIF 类型图片,否则将出错。*
181 | 欲嵌入的 GIF 背景图,设为 null 以禁用。
182 | 默认为 *null*。
183 |
184 | #### .backgroundGifCropRect(RectF)
185 |
186 | 裁切 GIF 所使用的矩形区域。
187 | 默认为 *null*。
188 |
189 | #### .saveTo(File)
190 |
191 | *设置 backgroundGif 时必须指定。*
192 | 生成 GIF QR 二维码的输出文件。
193 | 默认为 *null*。
194 |
195 | ### Logo
196 |
197 | #### .logo(Bitmap)
198 |
199 | 欲嵌入至二维码中心的 Logo,设为 null 以禁用。
200 | 默认为 *null*。
201 |
202 | #### .logoMargin(int)
203 |
204 | Logo 周围的空白边框,设为 0 以禁用。
205 | 单位是 *px*。
206 | 默认为 *10*。
207 |
208 | #### .logoCornerRadius(int)
209 |
210 | Logo 及其边框的圆角半径,设为 0 以禁用。
211 | 单位是 *px*。
212 | 默认为 *8*。
213 |
214 | #### .logoScale(float)
215 |
216 | 用于计算 Logo 大小的比例,过大可能会导致解码问题。
217 | 默认为 *0.2f*。
218 |
219 | ```
220 | ... .render(); // 得到 Bitmap
221 | ```
222 |
223 | ### 更新日志
224 |
225 | #### 1.1.1 版本
226 |
227 | - 修复了上一版本中生成的 QR 二维码图块间出现空白间隙的问题
228 |
229 | #### 1.1.0 版本
230 |
231 | - 加入 GIF 支持
232 | - 修复既有问题
233 |
234 | #### 1.0.6 版本
235 |
236 | - 修复 divide by zero 错误
237 |
238 | #### 1.0.5 版本
239 |
240 | - 使用 AwesomeQRCode 的方式变的更优雅
241 |
242 | #### 1.0.4 版本
243 |
244 | - 可以在二维码中选择嵌入 Logo
245 | - 演示应用更新
246 |
247 | #### 1.0.3 版本
248 |
249 | - 在二维码中的 Hints 中加入 CHARACTER_SET => UTF-8
250 | - 修复 [#7](https://github.com/SumiMakito/AwesomeQRCode/issues/7) 中提到的编码问题
251 |
252 | #### 1.0.2 版本
253 |
254 | - 加入使用圆点绘制二维码数据点的选项
255 |
256 | #### 1.0.1 版本
257 |
258 | - 加入背景二值化的支持
259 |
260 | #### 1.0.0 版本
261 |
262 | - 初次发布
263 |
264 | ## 相关项目
265 |
266 | ### Swift 下的 EFQRCode
267 |
268 | AwesomeQRCode 受 [由 EyreFree 创造的 EFQRCode](https://github.com/EyreFree/EFQRCode) 所启发而生,它是一个轻量级的、用来生成和识别二维码的纯 Swift 库,可根据输入的水印图和图标产生艺术二维码,基于 CoreImage 进行开发。受 qrcode 启发。EFQRCode 为你提供了一种更好的在你的 App 中操作二维码的方式。
269 |
270 | ### 可在网页使用的 JavaScript 版: Awesome-qr.js
271 |
272 | 详情请至 [Awesome-qr.js](https://github.com/SumiMakito/Awesome-qr.js)
273 |
274 | ### 强大的 Kotlin (复刻)版: AwesomeQRCode-Kotlin
275 |
276 | 更新较慢,不推荐使用。
277 |
278 | 详情请至 [AwesomeQRCode-Kotlin](https://github.com/SumiMakito/AwesomeQRCode-Kotlin)
279 |
280 | ## 捐赠
281 |
282 | 可以请我喝一杯卡布奇诺吗?
283 |
284 | PayPal | 支付宝
285 | ----|----
286 | [PayPal](https://www.paypal.me/makito) | [支付宝](https://qr.alipay.com/a6x02021re1jk4ftcymlw79)
287 |
288 | ## 版权信息与授权许可
289 |
290 |
291 |
292 | AwesomeQRCode is available under the Apache-2.0 license. See the LICENSE file for more info.
293 | Copyright © 2017-2018 Makito.
294 |
295 | ## 排他性发行协议
296 |
297 | 包含、引用、修改、再分发或使用本项目,即代表您已阅读并同意本排他性发行协议中的条款。
298 |
299 | **当再发行此开源软件时,此协议需与 APACHE 2.0 许可一同提供。**
300 |
301 | 您**可以**:
302 |
303 | - 在项目中使用 AwesomeQRCode(商业与非商业项目皆可)。
304 | - 按需要修改 AwesomeQRCode 的代码。
305 | - 按照本排他性发行协议以及 APACHE 2.0 许可再发行修改后的代码。
306 |
307 | 您**不可以**:
308 |
309 | - 使用 AwesomeQRCode 作为您应用的**主要**或**唯一**功能。
310 | - 将 AwesomeQRCode 的**部分**或**全部**内容作为可销售商品。
311 | - 制作 AwesomeQRCode 的演示应用,并提交至商店(包括但不限于 Google Play Store)。
312 |
313 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://jitpack.io/#SumiMakito/AwesomeQRCode)
4 | [](https://github.com/SumiMakito/AwesomeQRCode/releases/latest)
5 | [](https://github.com/SumiMakito/AwesomeQRCode/blob/master/LICENSE)
6 | 
7 |
8 | Awesome QR code - An awesome QR code generator for Android.
9 |
10 | ## Yay! Available on Google Play!
11 |
12 | *With the Awesome QR app, you can play with these options like a master!*
13 |
14 |
15 |
16 | ## Showcase
17 |
18 | No Logo|With Logo|Animated GIF
19 | ------------ | ------------- | -------------
20 |
|
|
21 |
22 | > Listing only several styles for demonstration.
23 | >
24 | > Find out more styles and options in the Awesome QR app!
25 |
26 | ## Installation
27 |
28 | To add the dependency into your project, edit your project-level *build.gradle* first.
29 |
30 | ```
31 | allprojects {
32 | repositories {
33 | ...
34 | maven { url 'https://jitpack.io' }
35 | }
36 | }
37 | ```
38 |
39 | Then, edit your *build.gradle* on module level.
40 |
41 | > Remember to replace `` with the latest version name showed on the JitPack badge.
42 |
43 | ```
44 | dependencies {
45 | implementation 'com.github.SumiMakito:AwesomeQRCode:'
46 | }
47 | ```
48 |
49 | ## Usage
50 |
51 | ### 1. Say hello to *RenderOption*.
52 |
53 | Like a recipe, *RenderOption* stores a set of options and it will tell the renderer "how to stylize the QR code for you."
54 |
55 | ```kotlin
56 | // Kotlin
57 |
58 | val renderOption = RenderOption()
59 | renderOption.content = "Special, thus awesome." // content to encode
60 | renderOption.size = 800 // size of the final QR code image
61 | renderOption.borderWidth = 20 // width of the empty space around the QR code
62 | renderOption.ecl = ErrorCorrectionLevel.M // (optional) specify an error correction level
63 | renderOption.patternScale = 0.35f // (optional) specify a scale for patterns
64 | renderOption.roundedPatterns = true // (optional) if true, blocks will be drawn as dots instead
65 | renderOption.clearBorder = true // if set to true, the background will NOT be drawn on the border area
66 | renderOption.color = color // set a color palette for the QR code
67 | renderOption.background = background // set a background, keep reading to find more about it
68 | renderOption.logo = logo // set a logo, keep reading to find more about it
69 | ```
70 |
71 | ```java
72 | // Java
73 |
74 | RenderOption renderOption = new RenderOption();
75 | renderOption.setContent("Special, thus awesome."); // content to encode
76 | renderOption.setSize(800); // size of the final QR code image
77 | renderOption.setBorderWidth(20); // width of the empty space around the QR code
78 | renderOption.setEcl(ErrorCorrectionLevel.M); // (optional) specify an error correction level
79 | renderOption.setPatternScale(0.35f); // (optional) specify a scale for patterns
80 | renderOption.setRoundedPatterns(true); // (optional) if true, blocks will be drawn as dots instead
81 | renderOption.setClearBorder(true); // if set to true, the background will NOT be drawn on the border area
82 | renderOption.setColor(color); // set a color palette for the QR code
83 | renderOption.setBackground(background); // set a background, keep reading to find more about it
84 | renderOption.setLogo(logo); // set a logo, keep reading to find more about it
85 | ```
86 |
87 | > But, wait. What is a *background*? Don't worry and keep reading. :)
88 |
89 | ### 2. Grab a background.Optional
90 |
91 | Awesome QR code natively provides three types of backgrounds. Each background should extend the abstract *Background* class.
92 |
93 | ```kotlin
94 | // Kotlin
95 |
96 | // A still background (a still image as the background)
97 | val background = StillBackground()
98 | background.bitmap = backgroundBitmap // assign a bitmap as the background
99 | background.clippingRect = Rect(0, 0, 200, 200) // crop the background before applying
100 | background.alpha = 0.7f // alpha of the background to be drawn
101 |
102 | // A blend background (to draw a QR code onto an area of a still image)
103 | val background = BlendBackground()
104 | background.bitmap = backgroundBitmap
105 | background.clippingRect = Rect(0, 0, 200, 200)
106 | background.alpha = 0.7f
107 | background.borderRadius = 10 // radius for blending corners
108 |
109 | // A gif background (animated)
110 | val background = GifBackground()
111 | background.inputFile = gifFile // assign a file object of a gif image to this field
112 | background.outputFile = File(pictureStorage, "output.gif") // IMPORTANT: the output image will be saved to this file object
113 | background.clippingRect = Rect(0, 0, 200, 200)
114 | background.alpha = 0.7f
115 | ```
116 |
117 | ```java
118 | // Java
119 |
120 | // A still background (a still image as the background)
121 | StillBackground background = new StillBackground();
122 | background.setBitmap(backgroundBitmap); // assign a bitmap as the background
123 | background.setClippingRect(new Rect(0, 0, 200, 200));// crop the background before
124 | background.setAlpha(0.7f); // alpha of the background to be drawn
125 |
126 | // A blend background (to draw a QR code onto an area of a still image)
127 | BlendBackground background = new BlendBackground();
128 | background.setBitmap(backgroundBitmap);
129 | background.setClippingRect(new Rect(0, 0, 200, 200));
130 | background.setAlpha(0.7f);
131 | background.setBorderRadius(10); // radius for blending corners
132 |
133 | // A gif background (animated)
134 | GifBackground background = new GifBackground();
135 | background.setInputFile(gifFile); // assign a file object of a gif image to this field
136 | background.setOutputFile(new File(pictureStorage, "output.gif")); // IMPORTANT: the output image will be saved to this file object
137 | background.setClippingRect(new Rect(0, 0, 200, 200));
138 | background.setAlpha(0.7f);
139 | ```
140 |
141 | ### 3. Seek for a rainbow.Optional
142 |
143 | > This step is optional since Awesome QR code will use black and white as the default color set.
144 |
145 | ```kotlin
146 | // Kotlin
147 |
148 | val color = Color()
149 | color.light = 0xFFFFFFFF.toInt() // for blank spaces
150 | color.dark = 0xFFFF8C8C.toInt() // for non-blank spaces
151 | color.background = 0xFFFFFFFF.toInt() // for the background (will be overriden by background images, if set)
152 | color.auto = false // set to true to automatically pick out colors from the background image (will only work if background image is present)
153 | ```
154 |
155 | ```java
156 | // Java
157 |
158 | Color color = new Color();
159 | color.setLight(0xFFFFFFFF); // for blank spaces
160 | color.setDark(0xFFFF8C8C); // for non-blank spaces
161 | color.setBackground(0xFFFFFFFF); // for the background (will be overriden by background images, if set)
162 | color.setAuto(false); // set to true to automatically pick out colors from the background image (will only work if background image is present)
163 | ```
164 |
165 | ### 4. Hey. I want a Logo.Optional
166 |
167 | > This step is optional since the logo is not required by default.
168 |
169 | ```kotlin
170 | // Kotlin
171 |
172 | val logo = Logo()
173 | logo.bitmap = logoBitmap
174 | logo.borderRadius = 10 // radius for logo's corners
175 | logo.borderWidth = 10 // width of the border to be added around the logo
176 | logo.scale = 0.3f // scale for the logo in the QR code
177 | logo.clippingRect = Rect(0, 0, 200, 200) // crop the logo image before applying it to the QR code
178 | ```
179 |
180 | ```java
181 | // Java
182 |
183 | Logo logo = new Logo();
184 | logo.setBitmap(logoBitmap);
185 | logo.setBorderRadius(10); // radius for logo's corners
186 | logo.setBorderWidth(10); // width of the border to be added around the logo
187 | logo.setScale(0.3f); // scale for the logo in the QR code
188 | logo.setClippingRect(new Rect(0, 0, 200, 200)); // crop the logo image before applying it to the QR code
189 | ```
190 |
191 | ### 5. Render!
192 |
193 | Meet the magical renderer.
194 |
195 | ##### If you prefer the asynchronous way...
196 |
197 | ```kotlin
198 | // Kotlin
199 |
200 | val result = AwesomeQrRenderer.renderAsync(renderOption, { result ->
201 | if (result.bitmap != null) {
202 | // play with the bitmap
203 | } else if (result.type == RenderResult.OutputType.GIF) {
204 | // If your Background is a GifBackground, the image
205 | // will be saved to the output file set in GifBackground
206 | // instead of being returned here. As a result, the
207 | // result.bitmap will be null.
208 | } else {
209 | // Oops, something gone wrong.
210 | }
211 | }, {
212 | exception -> exception.printStackTrace()
213 | // Oops, something gone wrong.
214 | })
215 | ```
216 |
217 | ##### Or synchronously...
218 |
219 | ```kotlin
220 | // Kotlin
221 |
222 | try {
223 | val result = AwesomeQrRenderer.render(renderOption)
224 | if (result.bitmap != null) {
225 | // play with the bitmap
226 | } else if (result.type == RenderResult.OutputType.GIF) {
227 | // If your Background is a GifBackground, the image
228 | // will be saved to the output file set in GifBackground
229 | // instead of being returned here. As a result, the
230 | // result.bitmap will be null.
231 | } else {
232 | // Oops, something gone wrong.
233 | }
234 | } catch (e: Exception) {
235 | e.printStackTrace()
236 | // Oops, something gone wrong.
237 | }
238 | ```
239 |
240 | ## Changelog
241 |
242 | #### Version 1.2.0
243 |
244 | - Translated into Kotlin.
245 | - Changed to the RenderOption-Renderer structure.
246 |
247 | #### Version 1.1.1
248 |
249 | - Fixed a bug that would previously cause the gaps between blocks in position/alignment patterns.
250 |
251 | #### Version 1.1.0
252 |
253 | + Added the support for GIF backgrounds.
254 | + Fixed some issues found in the previous version.
255 |
256 | #### Version 1.0.6
257 |
258 | - Fixed a "divide by zero" error mentioned in [#20](https://github.com/SumiMakito/AwesomeQRCode/issues/20).
259 |
260 | #### Version 1.0.5
261 | - The way to use Awesome QR code is more elegant.
262 |
263 | #### Version 1.0.4
264 |
265 | - New feature: Embedding a logo image in the QR code.
266 | - Sample/Demo application updated.
267 |
268 | #### Version 1.0.3
269 |
270 | - Added CHARACTER_SET => UTF-8 to QR code's hints before encoding.
271 | - Fixed an encoding issue mentioned in [#7](https://github.com/SumiMakito/AwesomeQRCode/issues/7).
272 |
273 | #### Version 1.0.2
274 |
275 | - Added an optional parameter which enables the data dots to appear as filled circles.
276 |
277 | #### Version 1.0.1
278 |
279 | - Now background images can be binarized as you like.
280 |
281 | #### Version 1.0.0
282 |
283 | - Initial release.
284 |
285 | ## Alternatives
286 |
287 | #### Awesome-qr.js written in JavaScript
288 |
289 | Redirect to [Awesome-qr.js](https://github.com/SumiMakito/Awesome-qr.js)
290 |
291 | #### EFQRCode written in Swift
292 |
293 | EFQRCode is a tool to generate QRCode image or recognize QRCode from image, in Swift.
294 |
295 | Awesome QR code is inspired by [EFQRCode by EyreFree](https://github.com/EyreFree/EFQRCode).
296 |
297 | If your application is in need of generating pretty QR codes in Swift, take a look at EFQRCode. It should help.
298 |
299 | ## Donation
300 |
301 | If you think Awesome QR code is awesome, would you like to buy me a cup of cappuccino?
302 |
303 | - [PayPal](https://www.paypal.me/makito)
304 | - [Alipay](https://qr.alipay.com/a6x02021re1jk4ftcymlw79)
305 |
306 | ## Sponsors
307 |
308 | It is those generous sponsors who supports this project makes the Awesome-qr.js more awesome!
309 |
310 | I'd like to express my sincere appreciation to all the generous sponsors.
311 |
312 | - [Coxxs](https://coxxs.me/)
313 |
314 | ## Special thanks
315 |
316 | - [Megabits](https://github.com/megabitsenmzq) (Banner art work)
317 |
318 | ## Copyright & License
319 |
320 |
321 |
322 | Awesome QR code is available under the Apache-2.0 license. See the LICENSE file for more info.
323 |
324 | Copyright © 2017-2018 Makito.
325 |
326 | ## Exclusive Distributor Agreement
327 |
328 | By including, importing, modifying, redistributing, or using this library, you acknowledge and agree that you have read and accept the terms of this Exclusive Distributor Agreement.
329 |
330 | **WHILE REDISTRIBUTING THIS LIBRARY, THIS AGREEMENT SHALL ALSO BE ATTACHED WITH THE APACHE-2.0 LICENSE.**
331 |
332 | You're **FREE** to:
333 |
334 | - Use Awesome QR code in your projects (commercial projects are okay as well).
335 |
336 | + Modify the code according to your needs.
337 | + Redistribute the modified code under the Exclusive Distributor Agreement and the Apache-2.0 license.
338 |
339 | You're **FORBIDDEN** to:
340 |
341 | + Make Awesome QR code the **main** or the **only** feature of your applications.
342 | + Treat the **whole or part** of Awesome QR code as a paid function.
343 | + Make a demo or sample application for Awesome QR code and submit the application to the store (IBNLT Google Play Store, etc.).
--------------------------------------------------------------------------------
/art/apache_feather.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
139 |
--------------------------------------------------------------------------------
/art/awesome-qr-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/awesome-qr-1.png
--------------------------------------------------------------------------------
/art/awesome-qr-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/awesome-qr-2.png
--------------------------------------------------------------------------------
/art/awesome-qr-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/awesome-qr-3.png
--------------------------------------------------------------------------------
/art/awesome-qr-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/awesome-qr-4.png
--------------------------------------------------------------------------------
/art/awesome-qr-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/awesome-qr-5.png
--------------------------------------------------------------------------------
/art/awesome-qr-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/awesome-qr-6.png
--------------------------------------------------------------------------------
/art/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/banner.png
--------------------------------------------------------------------------------
/art/banner2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/banner2.jpg
--------------------------------------------------------------------------------
/art/banner_new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/banner_new.png
--------------------------------------------------------------------------------
/art/banner_v3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/banner_v3.png
--------------------------------------------------------------------------------
/art/gif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/gif.gif
--------------------------------------------------------------------------------
/art/no_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/no_logo.png
--------------------------------------------------------------------------------
/art/play_store_badge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/play_store_badge.png
--------------------------------------------------------------------------------
/art/with_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/art/with_logo.png
--------------------------------------------------------------------------------
/awesomeqrcode-gif-stub/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif-stub/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 31
6 | defaultConfig {
7 | minSdkVersion 14
8 | targetSdkVersion 31
9 | versionCode rootProject.ext.version_code
10 | versionName rootProject.ext.version_name
11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
12 | }
13 | buildFeatures {
14 | buildConfig = false
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | compileOptions {
23 | sourceCompatibility JavaVersion.VERSION_1_8
24 | targetCompatibility JavaVersion.VERSION_1_8
25 | }
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(include: ['*.jar'], dir: 'libs')
30 |
31 | testImplementation 'junit:junit:4.13.2'
32 | androidTestImplementation 'androidx.test:runner:1.4.0'
33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
34 | }
35 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif-stub/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/makito/Android/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif-stub/src/androidTest/java/com/github/sumimakito/awesomeqrsample/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqrsample;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.github.sumimakito.awesomeqr.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif-stub/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif-stub/src/main/java/com/github/sumimakito/awesomeqr/GifPipeline.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.RectF
5 | import java.io.File
6 |
7 | class GifPipeline {
8 |
9 | var outputFile: File?
10 | get() {
11 | throw RuntimeException("STUB")
12 | }
13 | set(value) {
14 | throw RuntimeException("STUB")
15 | }
16 |
17 | var clippingRect: RectF?
18 | get() {
19 | throw RuntimeException("STUB")
20 | }
21 | set(value) {
22 | throw RuntimeException("STUB")
23 | }
24 |
25 | var errorInfo: String?
26 | get() {
27 | throw RuntimeException("STUB")
28 | }
29 | set(value) {
30 | throw RuntimeException("STUB")
31 | }
32 |
33 | fun init(file: File): Boolean {
34 | throw RuntimeException("STUB")
35 | }
36 |
37 | fun nextFrame(): Bitmap? {
38 | throw RuntimeException("STUB")
39 |
40 | }
41 |
42 | fun pushRendered(bitmap: Bitmap) {
43 | throw RuntimeException("STUB")
44 |
45 | }
46 |
47 | fun postRender(): Boolean {
48 | throw RuntimeException("STUB")
49 |
50 | }
51 |
52 | fun release(): Boolean {
53 | throw RuntimeException("STUB")
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif-stub/src/main/java/com/github/sumimakito/awesomeqr/option/background/Background.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr.option.background
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Rect
5 | import android.graphics.RectF
6 |
7 | abstract class Background @JvmOverloads constructor(
8 | var alpha: Float = 0.6f,
9 | var clippingRect: Rect? = null,
10 | var bitmap: Bitmap? = null
11 | ) {
12 | var clippingRectF: RectF?
13 | get() {
14 | throw RuntimeException("STUB")
15 | }
16 | set(value) {
17 | throw RuntimeException("STUB")
18 | }
19 |
20 | fun recycle() {
21 | throw RuntimeException("STUB")
22 | }
23 |
24 | abstract fun duplicate(): Background
25 |
26 | }
--------------------------------------------------------------------------------
/awesomeqrcode-gif-stub/src/main/java/com/github/sumimakito/awesomeqr/option/background/GifBackground.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr.option.background
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Rect
5 | import java.io.File
6 |
7 | class GifBackground @JvmOverloads constructor(
8 | var outputFile: File? = null,
9 | var inputFile: File? = null,
10 | alpha: Float = 0.6f,
11 | clippingRect: Rect? = null,
12 | bitmap: Bitmap? = null
13 | ) : Background(alpha, clippingRect, bitmap) {
14 |
15 | override fun duplicate(): GifBackground {
16 | throw RuntimeException("STUB")
17 | }
18 | }
--------------------------------------------------------------------------------
/awesomeqrcode-gif-stub/src/test/java/com/github/sumimakito/awesomeqrsample/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqrsample;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/awesomeqrcode-gif/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 31
6 | defaultConfig {
7 | minSdkVersion 14
8 | targetSdkVersion 31
9 | versionCode rootProject.ext.version_code
10 | versionName rootProject.ext.version_name
11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
12 | }
13 | buildFeatures {
14 | buildConfig = false
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | compileOptions {
23 | sourceCompatibility JavaVersion.VERSION_1_8
24 | targetCompatibility JavaVersion.VERSION_1_8
25 | }
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(include: ['*.jar'], dir: 'libs')
30 |
31 | testImplementation 'junit:junit:4.13.2'
32 | androidTestImplementation 'androidx.test:runner:1.4.0'
33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
34 |
35 | api project(':awesomeqrcode')
36 | implementation 'com.waynejo:androidndkgif:0.3.3'
37 | }
38 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/makito/Android/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif/src/androidTest/java/com/github/sumimakito/awesomeqrsample/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqrsample;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.github.sumimakito.awesomeqr.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif/src/main/java/com/github/sumimakito/awesomeqr/GifPipeline.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.RectF
5 | import com.waynejo.androidndkgif.GifDecoder
6 | import com.waynejo.androidndkgif.GifEncoder
7 | import java.io.File
8 | import java.io.FileNotFoundException
9 | import java.util.*
10 |
11 | class GifPipeline {
12 | var outputFile: File? = null
13 | var clippingRect: RectF? = null
14 | var errorInfo: String? = null
15 |
16 | private var gifDecoder: GifDecoder? = null
17 | private var frameSequence = LinkedList()
18 | private var currentFrame = 0
19 |
20 | fun init(file: File): Boolean {
21 | if (!file.exists()) {
22 | errorInfo = "ENOENT: File does not exist."
23 | return false
24 | } else if (file.isDirectory) {
25 | errorInfo = "EISDIR: Target is a directory."
26 | return false
27 | }
28 | gifDecoder = GifDecoder()
29 | val isSucceeded = gifDecoder!!.load(file.absolutePath)
30 | if (!isSucceeded) {
31 | errorInfo = "Failed to decode input file as GIF."
32 | return false
33 | }
34 | return true
35 | }
36 |
37 | fun nextFrame(): Bitmap? {
38 | if (gifDecoder!!.frameNum() == 0) {
39 | errorInfo = "GIF contains zero frames."
40 | return null
41 | }
42 | if (clippingRect == null) {
43 | errorInfo = "No cropping rect provided."
44 | return null
45 | }
46 | if (currentFrame < gifDecoder!!.frameNum()) {
47 | val frame = gifDecoder!!.frame(currentFrame)
48 | currentFrame++
49 | if (clippingRect != null) {
50 | val cropped = Bitmap.createBitmap(frame, Math.round(clippingRect!!.left), Math.round(clippingRect!!.top),
51 | Math.round(clippingRect!!.width()), Math.round(clippingRect!!.height()))
52 | frame.recycle()
53 | return cropped
54 | }
55 | return frame
56 | } else
57 | return null
58 | }
59 |
60 | fun pushRendered(bitmap: Bitmap) {
61 | frameSequence.addLast(bitmap)
62 | }
63 |
64 | fun postRender(): Boolean {
65 | if (outputFile == null) {
66 | errorInfo = "Output file is not yet set."
67 | return false
68 | }
69 |
70 | if (frameSequence.size == 0) {
71 | errorInfo = "Zero frames in the sequence."
72 | return false
73 | }
74 |
75 | try {
76 | val gifEncoder = GifEncoder()
77 | gifEncoder.init(frameSequence.first.width, frameSequence.first.height, outputFile!!.absolutePath, GifEncoder.EncodingType.ENCODING_TYPE_FAST)
78 | val frameIndex = 0
79 | while (!frameSequence.isEmpty()) {
80 | gifEncoder.encodeFrame(frameSequence.removeFirst(), gifDecoder!!.delay(frameIndex))
81 | }
82 | gifEncoder.close()
83 | } catch (e: FileNotFoundException) {
84 | e.printStackTrace()
85 | errorInfo = "FileNotFoundException. See stacktrace for more information."
86 | return false
87 | }
88 |
89 | return true
90 | }
91 |
92 | fun release(): Boolean {
93 | return true
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/awesomeqrcode-gif/src/main/java/com/github/sumimakito/awesomeqr/option/background/GifBackground.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr.option.background
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Rect
5 | import java.io.File
6 |
7 | class GifBackground @JvmOverloads constructor(
8 | var outputFile: File? = null,
9 | var inputFile: File? = null,
10 | alpha: Float = 0.6f,
11 | clippingRect: Rect? = null,
12 | bitmap: Bitmap? = null
13 | ) : Background(alpha, clippingRect, bitmap) {
14 | override fun duplicate(): GifBackground {
15 | return GifBackground(
16 | outputFile,
17 | inputFile,
18 | alpha,
19 | clippingRect,
20 | if (bitmap != null) bitmap!!.copy(Bitmap.Config.ARGB_8888, true) else null
21 | )
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/awesomeqrcode-gif/src/test/java/com/github/sumimakito/awesomeqrsample/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqrsample;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/awesomeqrcode/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/awesomeqrcode/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 31
6 | defaultConfig {
7 | minSdkVersion 14
8 | targetSdkVersion 31
9 | versionCode rootProject.ext.version_code
10 | versionName rootProject.ext.version_name
11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
12 | }
13 | buildFeatures {
14 | buildConfig = false
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | compileOptions {
23 | sourceCompatibility JavaVersion.VERSION_1_8
24 | targetCompatibility JavaVersion.VERSION_1_8
25 | }
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(include: ['*.jar'], dir: 'libs')
30 |
31 | testImplementation 'junit:junit:4.13.2'
32 | androidTestImplementation 'androidx.test:runner:1.4.0'
33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
34 |
35 | compileOnly project(':awesomeqrcode-gif-stub')
36 |
37 | implementation 'com.google.zxing:core:3.2.1'
38 | }
39 |
--------------------------------------------------------------------------------
/awesomeqrcode/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/makito/Android/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/awesomeqrcode/src/androidTest/java/com/github/sumimakito/awesomeqrsample/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqrsample;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.github.sumimakito.awesomeqr.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/awesomeqrcode/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/awesomeqrcode/src/main/java/com/github/sumimakito/awesomeqr/AwesomeQrRenderer.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr
2 |
3 | import android.graphics.*
4 | import com.github.sumimakito.awesomeqr.option.RenderOption
5 | import com.github.sumimakito.awesomeqr.option.background.BlendBackground
6 | import com.github.sumimakito.awesomeqr.option.background.GifBackground
7 | import com.github.sumimakito.awesomeqr.option.background.StillBackground
8 | import com.github.sumimakito.awesomeqr.util.RectUtils
9 | import com.google.zxing.EncodeHintType
10 | import com.google.zxing.WriterException
11 | import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
12 | import com.google.zxing.qrcode.encoder.ByteMatrix
13 | import com.google.zxing.qrcode.encoder.Encoder
14 | import com.google.zxing.qrcode.encoder.QRCode
15 | import java.util.*
16 | import kotlin.math.roundToInt
17 |
18 | open class AwesomeQrRenderer {
19 |
20 | /**
21 | * For more information about QR code, refer to: https://en.wikipedia.org/wiki/QR_code
22 | * BYTE_EPT: Empty block
23 | * BYTE_DTA: Data block
24 | * BYTE_POS: Position block
25 | * BYTE_AGN: Align block
26 | * BYTE_TMG: Timing block
27 | * BYTE_PTC: Protector block, translucent layer (custom block, this is not included in QR code's standards)
28 | */
29 | private val BYTE_EPT = 0x0.toByte()
30 | private val BYTE_DTA = 0x1.toByte()
31 | private val BYTE_POS = 0x2.toByte()
32 | private val BYTE_AGN = 0x3.toByte()
33 | private val BYTE_TMG = 0x4.toByte()
34 | private val BYTE_PTC = 0x5.toByte()
35 |
36 | @Throws(Exception::class)
37 | open fun render(renderOptions: RenderOption): RenderResult {
38 | if (renderOptions.background is BlendBackground && renderOptions.background!!.bitmap != null) {
39 | val background = renderOptions.background as BlendBackground
40 | var clippedBackground: Bitmap? = null
41 | if (background.clippingRect != null) {
42 | clippedBackground = Bitmap.createBitmap(
43 | background.bitmap!!,
44 | background.clippingRect!!.left.toFloat().roundToInt(),
45 | background.clippingRect!!.top.toFloat().roundToInt(),
46 | background.clippingRect!!.width().toFloat().roundToInt(),
47 | background.clippingRect!!.height().toFloat().roundToInt()
48 | )
49 | }
50 | val rendered = renderFrame(renderOptions, clippedBackground ?: background.bitmap)
51 | clippedBackground?.recycle()
52 | val scaledBoundingRects =
53 | scaleImageBoundingRectByClippingRect(background.bitmap!!, renderOptions.size, background.clippingRect)
54 | val fullRendered = Bitmap.createScaledBitmap(
55 | background.bitmap!!,
56 | scaledBoundingRects[0].width(),
57 | scaledBoundingRects[0].height(),
58 | true
59 | )
60 | val fullCanvas = Canvas(fullRendered)
61 | val paint = Paint()
62 | paint.isAntiAlias = true
63 | paint.color = renderOptions.color.background
64 | paint.isFilterBitmap = true
65 | // What a weird fix... Hope I can find the culprit...
66 | fullCanvas.drawBitmap(rendered, Rect(0, 0, rendered.width, rendered.height), scaledBoundingRects[1], paint)
67 | return RenderResult(fullRendered, null, RenderResult.OutputType.Blend)
68 | } else if (renderOptions.background is StillBackground) {
69 | val background = renderOptions.background as StillBackground
70 | var clippedBackground: Bitmap? = null
71 | if (background.clippingRect != null) {
72 | clippedBackground = Bitmap.createBitmap(
73 | background.bitmap!!,
74 | background.clippingRect!!.left.toFloat().roundToInt(),
75 | background.clippingRect!!.top.toFloat().roundToInt(),
76 | background.clippingRect!!.width().toFloat().roundToInt(),
77 | background.clippingRect!!.height().toFloat().roundToInt()
78 | )
79 | }
80 | val rendered = renderFrame(renderOptions, clippedBackground ?: background.bitmap)
81 | clippedBackground?.recycle()
82 | return RenderResult(rendered, null, RenderResult.OutputType.Still)
83 | } else if (renderOptions.background is GifBackground) {
84 | val background = renderOptions.background as GifBackground
85 | if (background.outputFile == null) {
86 | throw Exception("Output file has not yet been set. It is required under GIF background mode.")
87 | }
88 | val gifPipeline = GifPipeline()
89 | if (!gifPipeline.init(background.inputFile!!)) {
90 | throw Exception("GifPipeline failed to init: " + gifPipeline.errorInfo)
91 | }
92 | gifPipeline.clippingRect = background.clippingRectF!!
93 | gifPipeline.outputFile = background.outputFile!!
94 | var frame: Bitmap?
95 | var renderedFrame: Bitmap
96 | var firstRenderedFrame: Bitmap? = null
97 | frame = gifPipeline.nextFrame()
98 | while (frame != null) {
99 | renderedFrame = renderFrame(renderOptions, frame)
100 | gifPipeline.pushRendered(renderedFrame)
101 | if (firstRenderedFrame == null) {
102 | firstRenderedFrame = renderedFrame.copy(Bitmap.Config.ARGB_8888, true)
103 | }
104 | frame = gifPipeline.nextFrame()
105 | }
106 | if (gifPipeline.errorInfo != null) {
107 | throw Exception("GifPipeline failed to render frames: " + gifPipeline.errorInfo)
108 | }
109 | if (!gifPipeline.postRender()) {
110 | throw Exception("GifPipeline failed to do post render works: " + gifPipeline.errorInfo)
111 | }
112 | return RenderResult(firstRenderedFrame, background.outputFile, RenderResult.OutputType.GIF)
113 | } else {
114 | return RenderResult(renderFrame(renderOptions, null), null, RenderResult.OutputType.Still)
115 | }
116 | }
117 |
118 | open fun renderAsync(
119 | renderOptions: RenderOption,
120 | resultCallback: ((RenderResult) -> Unit)?,
121 | errorCallback: ((Exception) -> Unit)?
122 | ) {
123 | Thread {
124 | try {
125 | val renderResult = render(renderOptions)
126 | resultCallback?.invoke(renderResult)
127 | } catch (e: Exception) {
128 | errorCallback?.invoke(e)
129 | }
130 | }.start()
131 | }
132 |
133 | /**
134 | * The general render function.
135 | *
136 | * @return a Bitmap if success when under still background image mode
137 | * otherwise will return null if success
138 | * @throws Exception ONLY thrown when an error occurred
139 | */
140 | @Throws(Exception::class)
141 | protected fun renderFrame(renderOptions: RenderOption, backgroundFrame: Bitmap?): Bitmap {
142 | var backgroundFrameTemp = backgroundFrame
143 | if (renderOptions.content.isEmpty()) {
144 | throw IllegalArgumentException("Error: content is empty. (content.isEmpty())")
145 | }
146 | if (renderOptions.size < 0) {
147 | throw IllegalArgumentException("Error: a negative size is given. (size < 0)")
148 | }
149 | if (renderOptions.borderWidth < 0) {
150 | throw IllegalArgumentException("Error: a negative borderWidth is given. (borderWidth < 0)")
151 | }
152 | if (renderOptions.size - 2 * renderOptions.borderWidth <= 0) {
153 | throw IllegalArgumentException("Error: there is no space left for the QRCode. (size - 2 * borderWidth <= 0)")
154 | }
155 | val byteMatrix = getByteMatrix(renderOptions.content, renderOptions.ecl)
156 | ?: throw NullPointerException("Error: ByteMatrix based on content is null. (getByteMatrix(content, ecl) == null)")
157 | val innerRenderedSize = renderOptions.size - 2 * renderOptions.borderWidth
158 | val nCount = byteMatrix.width
159 | val nSize = Math.round(innerRenderedSize.toFloat() / nCount) // Avoid non-integer values
160 | val unscaledInnerRenderSize = nSize * nCount // Draw on unscaled Bitmap first
161 | val unscaledFullRenderSize = unscaledInnerRenderSize + 2 * renderOptions.borderWidth // Draw on unscaled Bitmap first
162 |
163 | if (renderOptions.size - 2 * renderOptions.borderWidth < byteMatrix.width) {
164 | throw IllegalArgumentException("Error: there is no space left for the QRCode. (size - 2 * borderWidth < " + byteMatrix.width + ")")
165 | }
166 | if (renderOptions.patternScale <= 0 || renderOptions.patternScale > 1) {
167 | throw IllegalArgumentException("Error: an illegal pattern scale is given. (patternScale <= 0 || patternScale > 1)")
168 | }
169 | if (renderOptions.logo != null && renderOptions.logo!!.bitmap != null) {
170 | val logo = renderOptions.logo!!
171 | if (logo.scale <= 0 || logo.scale > 0.5) {
172 | throw IllegalArgumentException("Error: an illegal logo scale is given. (logo.scale <= 0 || logo.scale > 0.5)")
173 | }
174 | if (logo.borderWidth < 0 || logo.borderWidth * 2 >= unscaledInnerRenderSize) {
175 | throw IllegalArgumentException("Error: an illegal logo border width is given. (logo.borderWidth < 0 || logo.borderWidth * 2 >= $unscaledInnerRenderSize)")
176 | }
177 | if (logo.borderRadius < 0) {
178 | throw IllegalArgumentException("Error: a negative logo border radius is given. (logo.borderRadius < 0)")
179 | }
180 | val logoScaledSize = (unscaledInnerRenderSize * logo.scale).toInt()
181 | if (logo.borderRadius * 2 > logoScaledSize) {
182 | throw IllegalArgumentException("Error: an illegal logo border radius is given. (logo.borderRadius * 2 > $logoScaledSize)")
183 | }
184 | }
185 |
186 | val backgroundDrawingRect = Rect(
187 | if (!renderOptions.clearBorder) 0 else renderOptions.borderWidth,
188 | if (!renderOptions.clearBorder) 0 else renderOptions.borderWidth,
189 | unscaledFullRenderSize - renderOptions.borderWidth * if (renderOptions.clearBorder) 1 else 0,
190 | unscaledFullRenderSize - renderOptions.borderWidth * if (renderOptions.clearBorder) 1 else 0
191 | )
192 |
193 | if (backgroundFrameTemp == null) {
194 | if (renderOptions.background is StillBackground
195 | || renderOptions.background is BlendBackground
196 | ) {
197 | backgroundFrameTemp = renderOptions.background!!.bitmap
198 | }
199 | }
200 |
201 | val unscaledFullRenderedBitmap =
202 | Bitmap.createBitmap(unscaledFullRenderSize, unscaledFullRenderSize, Bitmap.Config.ARGB_8888)
203 |
204 | if (renderOptions.color.auto && backgroundFrame != null) {
205 | renderOptions.color.light = -0x1
206 | renderOptions.color.dark = getDominantColor(backgroundFrame)
207 | }
208 |
209 | val paint = Paint()
210 | paint.isAntiAlias = true
211 | val paintBackground = Paint()
212 | paintBackground.isAntiAlias = true
213 | paintBackground.color = renderOptions.color.background
214 | paintBackground.style = Paint.Style.FILL
215 | val paintDark = Paint()
216 | paintDark.color = renderOptions.color.dark
217 | paintDark.isAntiAlias = true
218 | paintDark.style = Paint.Style.FILL
219 | val paintLight = Paint()
220 | paintLight.color = renderOptions.color.light
221 | paintLight.isAntiAlias = true
222 | paintLight.style = Paint.Style.FILL
223 | val paintProtector = Paint()
224 | paintProtector.color = Color.argb(120, 255, 255, 255)
225 | paintProtector.isAntiAlias = true
226 | paintProtector.style = Paint.Style.FILL
227 |
228 | val unscaledCanvas = Canvas(unscaledFullRenderedBitmap)
229 | unscaledCanvas.drawColor(Color.WHITE)
230 | unscaledCanvas.drawRect(
231 | (if (renderOptions.clearBorder) renderOptions.borderWidth else 0).toFloat(),
232 | (if (renderOptions.clearBorder) renderOptions.borderWidth else 0).toFloat(),
233 | (unscaledInnerRenderSize + if (renderOptions.clearBorder) renderOptions.borderWidth else renderOptions.borderWidth * 2).toFloat(),
234 | (unscaledInnerRenderSize + if (renderOptions.clearBorder) renderOptions.borderWidth else renderOptions.borderWidth * 2).toFloat(),
235 | paintBackground
236 | )
237 | if (backgroundFrame != null && renderOptions.background != null) {
238 | paint.alpha = (255 * renderOptions.background!!.alpha).roundToInt()
239 | unscaledCanvas.drawBitmap(
240 | backgroundFrame, null,
241 | backgroundDrawingRect,
242 | paint
243 | )
244 | }
245 | paint.alpha = 255
246 |
247 | for (row in 0 until byteMatrix.height) {
248 | for (col in 0 until byteMatrix.width) {
249 | when (byteMatrix.get(col, row)) {
250 | BYTE_AGN, BYTE_POS, BYTE_TMG -> unscaledCanvas.drawRect(
251 | (renderOptions.borderWidth + col * nSize).toFloat(),
252 | (renderOptions.borderWidth + row * nSize).toFloat(),
253 | (renderOptions.borderWidth + (col + 1) * nSize).toFloat(),
254 | (renderOptions.borderWidth + (row + 1) * nSize).toFloat(),
255 | paintDark
256 | )
257 | BYTE_DTA -> if (renderOptions.roundedPatterns) {
258 | unscaledCanvas.drawCircle(
259 | renderOptions.borderWidth + (col + 0.5f) * nSize,
260 | renderOptions.borderWidth + (row + 0.5f) * nSize,
261 | renderOptions.patternScale * nSize.toFloat() * 0.5f,
262 | paintDark
263 | )
264 | } else {
265 | unscaledCanvas.drawRect(
266 | renderOptions.borderWidth + (col + 0.5f * (1 - renderOptions.patternScale)) * nSize,
267 | renderOptions.borderWidth + (row + 0.5f * (1 - renderOptions.patternScale)) * nSize,
268 | renderOptions.borderWidth + (col + 0.5f * (1 + renderOptions.patternScale)) * nSize,
269 | renderOptions.borderWidth + (row + 0.5f * (1 + renderOptions.patternScale)) * nSize,
270 | paintDark
271 | )
272 | }
273 | BYTE_PTC -> unscaledCanvas.drawRect(
274 | (renderOptions.borderWidth + col * nSize).toFloat(),
275 | (renderOptions.borderWidth + row * nSize).toFloat(),
276 | (renderOptions.borderWidth + (col + 1) * nSize).toFloat(),
277 | (renderOptions.borderWidth + (row + 1) * nSize).toFloat(),
278 | paintProtector
279 | )
280 | BYTE_EPT -> if (renderOptions.roundedPatterns) {
281 | unscaledCanvas.drawCircle(
282 | renderOptions.borderWidth + (col + 0.5f) * nSize,
283 | renderOptions.borderWidth + (row + 0.5f) * nSize,
284 | renderOptions.patternScale * nSize.toFloat() * 0.5f,
285 | paintLight
286 | )
287 | } else {
288 | unscaledCanvas.drawRect(
289 | renderOptions.borderWidth + (col + 0.5f * (1 - renderOptions.patternScale)) * nSize,
290 | renderOptions.borderWidth + (row + 0.5f * (1 - renderOptions.patternScale)) * nSize,
291 | renderOptions.borderWidth + (col + 0.5f * (1 + renderOptions.patternScale)) * nSize,
292 | renderOptions.borderWidth + (row + 0.5f * (1 + renderOptions.patternScale)) * nSize,
293 | paintLight
294 | )
295 | }
296 | }
297 | }
298 | }
299 |
300 | if (renderOptions.logo != null && renderOptions.logo!!.bitmap != null) {
301 | val logo = renderOptions.logo!!
302 | val logoScaledSize = (unscaledInnerRenderSize * logo.scale).toInt()
303 | val logoScaled = Bitmap.createScaledBitmap(logo.bitmap!!, logoScaledSize, logoScaledSize, true)
304 | val logoOpt = Bitmap.createBitmap(logoScaled.width, logoScaled.height, Bitmap.Config.ARGB_8888)
305 | val logoCanvas = Canvas(logoOpt)
306 | val logoRect = Rect(0, 0, logoScaled.width, logoScaled.height)
307 | val logoRectF = RectF(logoRect)
308 | val logoPaint = Paint()
309 | logoPaint.isAntiAlias = true
310 | logoPaint.color = -0x1
311 | logoPaint.style = Paint.Style.FILL
312 | logoCanvas.drawARGB(0, 0, 0, 0)
313 | logoCanvas.drawRoundRect(logoRectF, logo.borderRadius.toFloat(), logo.borderRadius.toFloat(), logoPaint)
314 | logoPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
315 | logoCanvas.drawBitmap(logoScaled, logoRect, logoRect, logoPaint)
316 | logoPaint.color = renderOptions.color.light
317 | logoPaint.style = Paint.Style.STROKE
318 | logoPaint.strokeWidth = logo.borderWidth.toFloat()
319 | logoCanvas.drawRoundRect(logoRectF, logo.borderRadius.toFloat(), logo.borderRadius.toFloat(), logoPaint)
320 | unscaledCanvas.drawBitmap(
321 | logoOpt, (0.5 * (unscaledFullRenderedBitmap.width - logoOpt.width)).toInt().toFloat(),
322 | (0.5 * (unscaledFullRenderedBitmap.height - logoOpt.height)).toInt().toFloat(), paint
323 | )
324 | }
325 |
326 | val renderedScaledBitmap = Bitmap.createBitmap(
327 | renderOptions.size,
328 | renderOptions.size,
329 | Bitmap.Config.ARGB_8888
330 | )
331 |
332 | val scaledCanvas = Canvas(renderedScaledBitmap)
333 | scaledCanvas.drawBitmap(
334 | unscaledFullRenderedBitmap,
335 | null,
336 | Rect(0, 0, renderedScaledBitmap.width, renderedScaledBitmap.height),
337 | paint
338 | )
339 |
340 | val renderedResultBitmap: Bitmap
341 | if (renderOptions.background is BlendBackground) {
342 | renderedResultBitmap =
343 | Bitmap.createBitmap(renderedScaledBitmap.width, renderedScaledBitmap.height, Bitmap.Config.ARGB_8888)
344 | val finalRenderedCanvas = Canvas(renderedResultBitmap)
345 | val finalClippingRect = Rect(0, 0, renderedScaledBitmap.width, renderedScaledBitmap.height)
346 | val finalClippingRectF = RectF(finalClippingRect)
347 | val finalClippingPaint = Paint()
348 | finalClippingPaint.isAntiAlias = true
349 | finalClippingPaint.color = -0x1
350 | finalClippingPaint.style = Paint.Style.FILL
351 | finalRenderedCanvas.drawARGB(0, 0, 0, 0)
352 | finalRenderedCanvas.drawRoundRect(
353 | finalClippingRectF,
354 | (renderOptions.background as BlendBackground).borderRadius.toFloat(),
355 | (renderOptions.background as BlendBackground).borderRadius.toFloat(),
356 | finalClippingPaint
357 | )
358 | finalClippingPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
359 | finalRenderedCanvas.drawBitmap(renderedScaledBitmap, null, finalClippingRect, finalClippingPaint)
360 |
361 | renderedScaledBitmap.recycle()
362 | } else {
363 | renderedResultBitmap = renderedScaledBitmap
364 | }
365 | unscaledFullRenderedBitmap.recycle()
366 |
367 | return renderedResultBitmap
368 | }
369 |
370 | private fun getByteMatrix(contents: String, errorCorrectionLevel: ErrorCorrectionLevel): ByteMatrix? {
371 | try {
372 | val qrCode = getProtoQrCode(contents, errorCorrectionLevel)
373 | val agnCenter = qrCode.version.alignmentPatternCenters
374 | val byteMatrix = qrCode.matrix
375 | val matSize = byteMatrix.width
376 | for (row in 0 until matSize) {
377 | for (col in 0 until matSize) {
378 | if (isTypeAGN(col, row, agnCenter, true)) {
379 | if (byteMatrix.get(col, row) != BYTE_EPT) {
380 | byteMatrix.set(col, row, BYTE_AGN)
381 | } else {
382 | byteMatrix.set(col, row, BYTE_PTC)
383 | }
384 | } else if (isTypePOS(col, row, matSize, true)) {
385 | if (byteMatrix.get(col, row) != BYTE_EPT) {
386 | byteMatrix.set(col, row, BYTE_POS)
387 | } else {
388 | byteMatrix.set(col, row, BYTE_PTC)
389 | }
390 | } else if (isTypeTMG(col, row, matSize)) {
391 | if (byteMatrix.get(col, row) != BYTE_EPT) {
392 | byteMatrix.set(col, row, BYTE_TMG)
393 | } else {
394 | byteMatrix.set(col, row, BYTE_PTC)
395 | }
396 | }
397 |
398 | if (isTypePOS(col, row, matSize, false)) {
399 | if (byteMatrix.get(col, row) == BYTE_EPT) {
400 | byteMatrix.set(col, row, BYTE_PTC)
401 | }
402 | }
403 | }
404 | }
405 | return byteMatrix
406 | } catch (e: WriterException) {
407 | e.printStackTrace()
408 | }
409 |
410 | return null
411 | }
412 |
413 | /**
414 | * @param contents Contents to encode.
415 | * @param errorCorrectionLevel ErrorCorrectionLevel
416 | * @return QR code object.
417 | * @throws WriterException Refer to the messages below.
418 | */
419 | @Throws(WriterException::class)
420 | private fun getProtoQrCode(contents: String, errorCorrectionLevel: ErrorCorrectionLevel): QRCode {
421 | if (contents.isEmpty()) {
422 | throw IllegalArgumentException("Found empty content.")
423 | }
424 | val hintMap = Hashtable()
425 | hintMap[EncodeHintType.CHARACTER_SET] = "UTF-8"
426 | hintMap[EncodeHintType.ERROR_CORRECTION] = errorCorrectionLevel
427 | return Encoder.encode(contents, errorCorrectionLevel, hintMap)
428 | }
429 |
430 | private fun isTypeAGN(x: Int, y: Int, agnCenter: IntArray, edgeOnly: Boolean): Boolean {
431 | if (agnCenter.isEmpty()) return false
432 | val edgeCenter = agnCenter[agnCenter.size - 1]
433 | for (agnY in agnCenter) {
434 | for (agnX in agnCenter) {
435 | if (edgeOnly && agnX != 6 && agnY != 6 && agnX != edgeCenter && agnY != edgeCenter)
436 | continue
437 | if (agnX == 6 && agnY == 6 || agnX == 6 && agnY == edgeCenter || agnY == 6 && agnX == edgeCenter)
438 | continue
439 | if (x >= agnX - 2 && x <= agnX + 2 && y >= agnY - 2 && y <= agnY + 2)
440 | return true
441 | }
442 | }
443 | return false
444 | }
445 |
446 | private fun isTypePOS(x: Int, y: Int, size: Int, inner: Boolean): Boolean {
447 | return if (inner) {
448 | x < 7 && (y < 7 || y >= size - 7) || x >= size - 7 && y < 7
449 | } else {
450 | x <= 7 && (y <= 7 || y >= size - 8) || x >= size - 8 && y <= 7
451 | }
452 | }
453 |
454 | private fun isTypeTMG(x: Int, y: Int, size: Int): Boolean {
455 | return y == 6 && x >= 8 && x < size - 8 || x == 6 && y >= 8 && y < size - 8
456 | }
457 |
458 | private fun scaleBitmap(src: Bitmap, dst: Bitmap) {
459 | val cPaint = Paint()
460 | cPaint.isAntiAlias = true
461 | cPaint.isDither = true
462 | cPaint.isFilterBitmap = true
463 |
464 | val ratioX = dst.width / src.width.toFloat()
465 | val ratioY = dst.height / src.height.toFloat()
466 | val middleX = dst.width * 0.5f
467 | val middleY = dst.height * 0.5f
468 |
469 | val scaleMatrix = Matrix()
470 | scaleMatrix.setScale(ratioX, ratioY, middleX, middleY)
471 | val canvas = Canvas(dst)
472 | canvas.setMatrix(scaleMatrix)
473 | canvas.drawBitmap(
474 | src, middleX - src.width / 2,
475 | middleY - src.height / 2, cPaint
476 | )
477 | }
478 |
479 | private fun getDominantColor(bitmap: Bitmap): Int {
480 | val newBitmap = Bitmap.createScaledBitmap(bitmap, 8, 8, true)
481 | var red = 0
482 | var green = 0
483 | var blue = 0
484 | var c = 0
485 | var r: Int
486 | var g: Int
487 | var b: Int
488 | for (y in 0 until newBitmap.height) {
489 | for (x in 0 until newBitmap.height) {
490 | val color = newBitmap.getPixel(x, y)
491 | r = color shr 16 and 0xFF
492 | g = color shr 8 and 0xFF
493 | b = color and 0xFF
494 | if (r > 200 || g > 200 || b > 200) continue
495 | red += r
496 | green += g
497 | blue += b
498 | c++
499 | }
500 | }
501 | newBitmap.recycle()
502 | if (c == 0) {
503 | // got a bitmap with no pixels in it
504 | // avoid the "divide by zero" error
505 | // but WHO DARES GIMME AN EMPTY BITMAP?
506 | return -0x1000000
507 | } else {
508 | red = (red / c).coerceIn(0, 0xff)
509 | green = (green / c).coerceIn(0, 0xff)
510 | blue = (blue / c).coerceIn(0, 0xff)
511 |
512 | val hsv = FloatArray(3)
513 | Color.RGBToHSV(red, green, blue, hsv)
514 | hsv[2] = hsv[2].coerceAtLeast(0.7f)
515 |
516 | return 0xFF shl 24 or Color.HSVToColor(hsv) // (0xFF << 24) | (red << 16) | (green << 8) | blue;
517 | }
518 | }
519 |
520 | // returns [finalBoundingRect, newClippingRect]
521 | private fun scaleImageBoundingRectByClippingRect(bitmap: Bitmap, size: Int, clippingRect: Rect?): Array {
522 | if (clippingRect == null) return scaleImageBoundingRectByClippingRect(bitmap, size, Rect(0, 0, bitmap.width, bitmap.height))
523 | if (clippingRect.width() != clippingRect.height() || clippingRect.width() <= size) {
524 | return arrayOf(Rect(0, 0, bitmap.width, bitmap.height), clippingRect)
525 | }
526 | val clippingSize = clippingRect.width().toFloat()
527 | val scalingRatio = size / clippingSize
528 | return arrayOf(
529 | RectUtils.round(
530 | RectF(
531 | 0f, 0f,
532 | bitmap.width * scalingRatio, bitmap.height * scalingRatio
533 | )
534 | ),
535 | RectUtils.round(
536 | RectF(
537 | clippingRect.left * scalingRatio, clippingRect.top * scalingRatio,
538 | clippingRect.right * scalingRatio, clippingRect.bottom * scalingRatio
539 | )
540 | )
541 | )
542 | }
543 | }
--------------------------------------------------------------------------------
/awesomeqrcode/src/main/java/com/github/sumimakito/awesomeqr/RenderResult.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr
2 |
3 | import android.graphics.Bitmap
4 | import java.io.File
5 |
6 | class RenderResult @JvmOverloads constructor(val bitmap: Bitmap?,
7 | val gifOutputFile: File?,
8 | val type: OutputType = OutputType.Still) {
9 | enum class OutputType {
10 | Still, GIF, Blend
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/awesomeqrcode/src/main/java/com/github/sumimakito/awesomeqr/option/RenderOption.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr.option
2 |
3 | import com.github.sumimakito.awesomeqr.option.background.Background
4 | import com.github.sumimakito.awesomeqr.option.color.Color
5 | import com.github.sumimakito.awesomeqr.option.logo.Logo
6 | import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
7 |
8 | class RenderOption {
9 | var content = "Makes QR Codes Great Again."
10 | var size = 600
11 | var borderWidth = 30
12 | var clearBorder = true
13 | var patternScale = 0.4f
14 | var roundedPatterns = false
15 | var color: Color = Color()
16 | var ecl = ErrorCorrectionLevel.M
17 |
18 | var background: Background? = null
19 | set(value) {
20 | if (field != null) {
21 | field!!.recycle()
22 | }
23 | field = value
24 | }
25 |
26 | var logo: Logo? = null
27 | set(value) {
28 | if (field != null) {
29 | field!!.recycle()
30 | }
31 | field = value
32 | }
33 |
34 | fun recycle() {
35 | if (background != null) {
36 | background!!.recycle()
37 | background = null
38 | }
39 | if (logo != null) {
40 | logo!!.recycle()
41 | logo = null
42 | }
43 | }
44 |
45 | fun duplicate(): RenderOption {
46 | val renderConfig = RenderOption()
47 | renderConfig.content = content
48 | renderConfig.size = size
49 | renderConfig.borderWidth = borderWidth
50 | renderConfig.clearBorder = clearBorder
51 | renderConfig.patternScale = patternScale
52 | renderConfig.roundedPatterns = roundedPatterns
53 | renderConfig.color = color.duplicate()
54 | renderConfig.ecl = ecl
55 |
56 | renderConfig.background = background?.duplicate()
57 | renderConfig.logo = logo?.duplicate()
58 | return renderConfig
59 | }
60 | }
--------------------------------------------------------------------------------
/awesomeqrcode/src/main/java/com/github/sumimakito/awesomeqr/option/background/Background.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr.option.background
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Rect
5 | import android.graphics.RectF
6 | import com.github.sumimakito.awesomeqr.option.RenderOption
7 | import com.github.sumimakito.awesomeqr.util.RectUtils
8 |
9 | abstract class Background @JvmOverloads constructor(
10 | var alpha: Float = 0.6f,
11 | var clippingRect: Rect? = null,
12 | var bitmap: Bitmap? = null
13 | ) {
14 | var clippingRectF: RectF?
15 | get() {
16 | return if (clippingRect == null) null else RectF(clippingRect)
17 | }
18 | set(value) {
19 | if (value != null) this.clippingRect = RectUtils.round(value)
20 | else clippingRect = null
21 | }
22 |
23 | fun recycle() {
24 | if (bitmap == null) return
25 | if (bitmap!!.isRecycled) return
26 | bitmap!!.recycle()
27 | bitmap = null
28 | }
29 |
30 | abstract fun duplicate(): Background
31 |
32 | }
--------------------------------------------------------------------------------
/awesomeqrcode/src/main/java/com/github/sumimakito/awesomeqr/option/background/BlendBackground.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr.option.background
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Rect
5 |
6 | class BlendBackground @JvmOverloads constructor(var borderRadius: Int = 10,
7 | alpha: Float = 0.6f,
8 | clippingRect: Rect? = null,
9 | bitmap: Bitmap? = null) : Background(alpha, clippingRect, bitmap) {
10 | override fun duplicate(): BlendBackground {
11 | return BlendBackground(borderRadius,
12 | alpha,
13 | clippingRect,
14 | if (bitmap != null) bitmap!!.copy(Bitmap.Config.ARGB_8888, true) else null
15 | )
16 | }
17 | }
--------------------------------------------------------------------------------
/awesomeqrcode/src/main/java/com/github/sumimakito/awesomeqr/option/background/StillBackground.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr.option.background
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Rect
5 |
6 | class StillBackground @JvmOverloads constructor(alpha: Float = 0.6f,
7 | clippingRect: Rect? = null,
8 | bitmap: Bitmap? = null) : Background(alpha, clippingRect, bitmap) {
9 | override fun duplicate(): StillBackground {
10 | return StillBackground(
11 | alpha,
12 | clippingRect,
13 | if (bitmap != null) bitmap!!.copy(Bitmap.Config.ARGB_8888, true) else null
14 | )
15 | }
16 | }
--------------------------------------------------------------------------------
/awesomeqrcode/src/main/java/com/github/sumimakito/awesomeqr/option/color/Color.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr.option.color
2 |
3 | class Color(var auto: Boolean = false,
4 | var background: Int = 0xFFFFBBAA.toInt(),
5 | var light: Int = 0xFFFFFFFF.toInt(),
6 | var dark: Int = 0xFFE57373.toInt()) {
7 | fun duplicate(): Color {
8 | return Color(auto, background, light, dark)
9 | }
10 | }
--------------------------------------------------------------------------------
/awesomeqrcode/src/main/java/com/github/sumimakito/awesomeqr/option/logo/Logo.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr.option.logo
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.RectF
5 |
6 | class Logo(var bitmap: Bitmap? = null,
7 | var scale: Float = 0.2f,
8 | var borderRadius: Int = 8,
9 | var borderWidth: Int = 10,
10 | var clippingRect: RectF? = null) {
11 | fun recycle() {
12 | if (bitmap == null) return
13 | if (bitmap!!.isRecycled) return
14 | bitmap!!.recycle()
15 | bitmap = null
16 | }
17 |
18 | fun duplicate(): Logo {
19 | return Logo(
20 | if (bitmap != null) bitmap!!.copy(Bitmap.Config.ARGB_8888, true) else null,
21 | scale,
22 | borderRadius,
23 | borderWidth,
24 | clippingRect
25 | )
26 | }
27 | }
--------------------------------------------------------------------------------
/awesomeqrcode/src/main/java/com/github/sumimakito/awesomeqr/util/RectUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqr.util
2 |
3 | import android.graphics.Rect
4 | import android.graphics.RectF
5 |
6 | object RectUtils {
7 | fun round(rectF: RectF): Rect {
8 | return Rect(Math.round(rectF.left), Math.round(rectF.top), Math.round(rectF.right), Math.round(rectF.bottom))
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/awesomeqrcode/src/test/java/com/github/sumimakito/awesomeqrsample/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.github.sumimakito.awesomeqrsample;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext.kotlin_version = '1.5.21'
4 | ext.version_code = 11
5 | ext.version_name = '2.0.0'
6 | repositories {
7 | google()
8 | mavenCentral()
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:4.2.2'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | mavenCentral()
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## Project-wide Gradle settings.
2 | #
3 | # For more details on how to configure your build environment visit
4 | # http://www.gradle.org/docs/current/userguide/build_environment.html
5 | #
6 | # Specifies the JVM arguments used for the daemon process.
7 | # The setting is particularly useful for tweaking memory settings.
8 | # Default value: -Xmx1024m -XX:MaxPermSize=256m
9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
10 | #
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | #Fri Mar 02 00:25:42 CST 2018
16 | systemProp.http.proxyHost=127.0.0.1
17 | org.gradle.jvmargs=-Xmx1536m
18 | systemProp.http.proxyPort=8123
19 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RikkaW/AwesomeQRCode/9564dd96225b7e7d5a4848e78e9c8c87c55a2c93/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jul 12 21:05:17 CST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':awesomeqrcode', ':awesomeqrcode-gif', ':awesomeqrcode-gif-stub'
2 |
--------------------------------------------------------------------------------