├── .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 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | Special, thus awesome. 4 | 5 | 6 | 7 | [![](https://jitpack.io/v/SumiMakito/AwesomeQRCode.svg)](https://jitpack.io/#SumiMakito/AwesomeQRCode) 8 | [![release](https://img.shields.io/github/release/SumiMakito/AwesomeQRCode.svg)](https://github.com/SumiMakito/AwesomeQRCode/releases/latest) 9 | [![license](https://img.shields.io/github/license/SumiMakito/AwesomeQRCode.svg)](https://github.com/SumiMakito/AwesomeQRCode/blob/master/LICENSE) 10 | ![](https://img.shields.io/badge/made%20with-%3C3-orange.svg) 11 | 12 | AwesomeQRCode - 一个优雅的(不起眼的) QR 二维码生成器 13 | 14 | > [Swithc to English Version?](README.md) 15 | 16 | ## 好耶! 演示应用! 17 | 18 | Google Play Store 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 | Apache-2.0 license 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 | Special, thus awesome. 2 | 3 | [![](https://jitpack.io/v/SumiMakito/AwesomeQRCode.svg)](https://jitpack.io/#SumiMakito/AwesomeQRCode) 4 | [![release](https://img.shields.io/github/release/SumiMakito/AwesomeQRCode.svg)](https://github.com/SumiMakito/AwesomeQRCode/releases/latest) 5 | [![license](https://img.shields.io/github/license/SumiMakito/AwesomeQRCode.svg)](https://github.com/SumiMakito/AwesomeQRCode/blob/master/LICENSE) 6 | ![](https://img.shields.io/badge/made%20with-cappuccino-orange.svg) 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 | Google Play Store 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 | Apache-2.0 license 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 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 16 | 17 | 24 | 25 | 27 | 29 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 126 | 127 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 138 | 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 | --------------------------------------------------------------------------------