├── .gitignore ├── .idea ├── compiler.xml ├── encodings.xml ├── libraries │ ├── Maven__javax_servlet_javax_servlet_api_3_1_0.xml │ └── Maven__junit_junit_4_7.xml ├── misc.xml ├── modules.xml ├── uiDesigner.xml └── vcs.xml ├── EasyCaptcha.iml ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── wf │ │ └── captcha │ │ ├── ArithmeticCaptcha.java │ │ ├── ChineseCaptcha.java │ │ ├── ChineseGifCaptcha.java │ │ ├── GifCaptcha.java │ │ ├── SpecCaptcha.java │ │ ├── base │ │ ├── ArithHelper.java │ │ ├── ArithmeticCaptchaAbstract.java │ │ ├── Calculator.java │ │ ├── Captcha.java │ │ ├── ChineseCaptchaAbstract.java │ │ └── Randoms.java │ │ ├── servlet │ │ └── CaptchaServlet.java │ │ └── utils │ │ ├── CaptchaUtil.java │ │ ├── Encoder.java │ │ ├── GifEncoder.java │ │ └── Quant.java └── resources │ ├── actionj.ttf │ ├── epilog.ttf │ ├── fresnel.ttf │ ├── headache.ttf │ ├── lexo.ttf │ ├── prefix.ttf │ ├── progbot.ttf │ ├── ransom.ttf │ ├── robot.ttf │ └── scandal.ttf └── test └── java └── com └── wf └── captcha └── CaptchaTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__javax_servlet_javax_servlet_api_3_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__junit_junit_4_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /EasyCaptcha.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyCaptcha 2 | 3 | ![MavenCentral](https://img.shields.io/maven-central/v/com.github.whvcse/easy-captcha?style=flat-square) 4 | ![Hex.pm](https://img.shields.io/hexpm/l/plug.svg?style=flat-square) 5 | 6 | 7 | ## 1.简介 8 |  Java图形验证码,支持gif、中文、算术等类型,可用于Java Web、JavaSE等项目。 9 | 10 | --- 11 | 12 | ## 2.效果展示 13 | 14 | ![验证码](https://s2.ax1x.com/2019/08/23/msFrE8.png) 15 |    16 | ![验证码](https://s2.ax1x.com/2019/08/23/msF0DP.png) 17 |    18 | ![验证码](https://s2.ax1x.com/2019/08/23/msFwut.png) 19 |
20 | ![验证码](https://s2.ax1x.com/2019/08/23/msFzVK.gif) 21 |    22 | ![验证码](https://s2.ax1x.com/2019/08/23/msFvb6.gif) 23 |    24 | ![验证码](https://s2.ax1x.com/2019/08/23/msFXK1.gif) 25 | 26 | **算术类型:** 27 | 28 | ![验证码](https://s2.ax1x.com/2019/08/23/mskKPg.png) 29 |    30 | ![验证码](https://s2.ax1x.com/2019/08/23/msknIS.png) 31 |    32 | ![验证码](https://s2.ax1x.com/2019/08/23/mskma8.png) 33 | 34 | **中文类型:** 35 | 36 | ![验证码](https://s2.ax1x.com/2019/08/23/mskcdK.png) 37 |    38 | ![验证码](https://s2.ax1x.com/2019/08/23/msk6Z6.png) 39 |    40 | ![验证码](https://s2.ax1x.com/2019/08/23/msksqx.png) 41 | 42 | **内置字体:** 43 | 44 | ![验证码](https://s2.ax1x.com/2019/08/23/msAVSJ.png) 45 |    46 | ![验证码](https://s2.ax1x.com/2019/08/23/msAAW4.png) 47 |    48 | ![验证码](https://s2.ax1x.com/2019/08/23/msAkYF.png) 49 | 50 | 51 | --- 52 | 53 | ## 3.导入项目 54 | 55 | ### 3.1.gradle方式的引入 56 | ```text 57 | dependencies { 58 | compile 'com.github.whvcse:easy-captcha:1.6.2' 59 | } 60 | ``` 61 | 62 | ### 3.2.maven方式引入 63 | ```xml 64 | 65 | 66 | com.github.whvcse 67 | easy-captcha 68 | 1.6.2 69 | 70 | 71 | ``` 72 | 73 | ### 3.3.jar包下载 74 | [easy-captcha-1.6.2.jar](https://gitee.com/whvse/EasyCaptcha/releases) 75 | 76 | maven导入jar包,在项目根目录创建`libs`文件夹,然后pom.xml添加如下: 77 | ``` 78 | 79 | com.github.whvcse 80 | easy-captcha 81 | 1.6.1 82 | ${basedir}/libs/easy-captcha-1.6.2.jar 83 | 84 | ``` 85 | 86 | --- 87 | 88 | ## 4.使用方法 89 | 90 | ### 4.1.在SpringMVC中使用 91 | ```java 92 | @Controller 93 | public class CaptchaController { 94 | 95 | @RequestMapping("/captcha") 96 | public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception { 97 | CaptchaUtil.out(request, response); 98 | } 99 | } 100 | ``` 101 | 前端html代码: 102 | ```html 103 | 104 | ``` 105 | 106 | > 不要忘了把`/captcha`路径排除登录拦截,比如shiro的拦截。 107 | 108 | ### 4.2.在servlet中使用 109 | web.xml中配置servlet: 110 | ```xml 111 | 112 | 113 | 114 | CaptchaServlet 115 | com.wf.captcha.servlet.CaptchaServlet 116 | 117 | 118 | CaptchaServlet 119 |    /captcha 120 | 121 | 122 | 123 | ``` 124 | 前端html代码: 125 | ```html 126 | 127 | ``` 128 | 129 | ### 4.3.判断验证码是否正确 130 | 131 | ```java 132 | @Controller 133 | public class LoginController { 134 | 135 | @PostMapping("/login") 136 | public JsonResult login(String username,String password,String verCode){ 137 | if (!CaptchaUtil.ver(verCode, request)) { 138 | CaptchaUtil.clear(request); // 清除session中的验证码 139 | return JsonResult.error("验证码不正确"); 140 | } 141 | } 142 | } 143 | ``` 144 | 145 | ### 4.4.设置宽高和位数 146 | ```java 147 | @Controller 148 | public class CaptchaController { 149 | 150 | @RequestMapping("/captcha") 151 | public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception { 152 | // 设置位数 153 | CaptchaUtil.out(5, request, response); 154 | // 设置宽、高、位数 155 | CaptchaUtil.out(130, 48, 5, request, response); 156 | 157 | // 使用gif验证码 158 | GifCaptcha gifCaptcha = new GifCaptcha(130,48,4); 159 | CaptchaUtil.out(gifCaptcha, request, response); 160 | } 161 | } 162 | ``` 163 | 164 | ### 4.5.不使用工具类 165 |  CaptchaUtil封装了输出验证码、存session、判断验证码等功能,也可以不使用此工具类: 166 | 167 | ```java 168 | @Controller 169 | public class CaptchaController { 170 | 171 | @RequestMapping("/captcha") 172 | public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception { 173 | // 设置请求头为输出图片类型 174 | response.setContentType("image/gif"); 175 | response.setHeader("Pragma", "No-cache"); 176 | response.setHeader("Cache-Control", "no-cache"); 177 | response.setDateHeader("Expires", 0); 178 | 179 | // 三个参数分别为宽、高、位数 180 | SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5); 181 | // 设置字体 182 | specCaptcha.setFont(new Font("Verdana", Font.PLAIN, 32)); // 有默认字体,可以不用设置 183 | // 设置类型,纯数字、纯字母、字母数字混合 184 | specCaptcha.setCharType(Captcha.TYPE_ONLY_NUMBER); 185 | 186 | // 验证码存入session 187 | request.getSession().setAttribute("captcha", specCaptcha.text().toLowerCase()); 188 | 189 | // 输出图片流 190 | specCaptcha.out(response.getOutputStream()); 191 | } 192 | 193 | @PostMapping("/login") 194 | public JsonResult login(String username,String password,String verCode){ 195 | // 获取session中的验证码 196 | String sessionCode = request.getSession().getAttribute("captcha"); 197 | // 判断验证码 198 | if (verCode==null || !sessionCode.equals(verCode.trim().toLowerCase())) { 199 | return JsonResult.error("验证码不正确"); 200 | } 201 | } 202 | } 203 | ``` 204 | 205 | ## 5.更多设置 206 | 207 | ### 5.1.验证码类型 208 | 209 | ```java 210 | public class Test { 211 | 212 | public static void main(String[] args) { 213 | // png类型 214 | SpecCaptcha captcha = new SpecCaptcha(130, 48); 215 | captcha.text(); // 获取验证码的字符 216 | captcha.textChar(); // 获取验证码的字符数组 217 | 218 | // gif类型 219 | GifCaptcha captcha = new GifCaptcha(130, 48); 220 | 221 | // 中文类型 222 | ChineseCaptcha captcha = new ChineseCaptcha(130, 48); 223 | 224 | // 中文gif类型 225 | ChineseGifCaptcha captcha = new ChineseGifCaptcha(130, 48); 226 | 227 | // 算术类型 228 | ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 48); 229 | captcha.setLen(3); // 几位数运算,默认是两位 230 | captcha.getArithmeticString(); // 获取运算的公式:3+2=? 231 | captcha.text(); // 获取运算的结果:5 232 | 233 | captcha.out(outputStream); // 输出验证码 234 | } 235 | } 236 | ``` 237 | 238 | > 注意:
239 | >  算术验证码的len表示是几位数运算,而其他验证码的len表示验证码的位数,算术验证码的text()表示的是公式的结果, 240 | > 对于算术验证码,你应该把公式的结果存储session,而不是公式。 241 | 242 | ### 5.2.验证码字符类型 243 | 244 | 类型 | 描述 245 | :--- | :--- 246 | TYPE_DEFAULT | 数字和字母混合 247 | TYPE_ONLY_NUMBER | 纯数字 248 | TYPE_ONLY_CHAR | 纯字母 249 | TYPE_ONLY_UPPER | 纯大写字母 250 | TYPE_ONLY_LOWER | 纯小写字母 251 | TYPE_NUM_AND_UPPER | 数字和大写字母 252 | 253 | 使用方法: 254 | ``` 255 | SpecCaptcha captcha = new SpecCaptcha(130, 48, 5); 256 | captcha.setCharType(Captcha.TYPE_ONLY_NUMBER); 257 | ``` 258 | 259 | > 只有`SpecCaptcha`和`GifCaptcha`设置才有效果。 260 | 261 | ### 5.3.字体设置 262 | 内置字体: 263 | 264 | 字体 | 效果 265 | :--- | :--- 266 | Captcha.FONT_1 | ![](https://s2.ax1x.com/2019/08/23/msMe6U.png) 267 | Captcha.FONT_2 | ![](https://s2.ax1x.com/2019/08/23/msMAf0.png) 268 | Captcha.FONT_3 | ![](https://s2.ax1x.com/2019/08/23/msMCwj.png) 269 | Captcha.FONT_4 | ![](https://s2.ax1x.com/2019/08/23/msM9mQ.png) 270 | Captcha.FONT_5 | ![](https://s2.ax1x.com/2019/08/23/msKz6S.png) 271 | Captcha.FONT_6 | ![](https://s2.ax1x.com/2019/08/23/msKxl8.png) 272 | Captcha.FONT_7 | ![](https://s2.ax1x.com/2019/08/23/msMPTs.png) 273 | Captcha.FONT_8 | ![](https://s2.ax1x.com/2019/08/23/msMmXF.png) 274 | Captcha.FONT_9 | ![](https://s2.ax1x.com/2019/08/23/msMVpV.png) 275 | Captcha.FONT_10 | ![](https://s2.ax1x.com/2019/08/23/msMZlT.png) 276 | 277 | 使用方法: 278 | ``` 279 | SpecCaptcha captcha = new SpecCaptcha(130, 48, 5); 280 | 281 | // 设置内置字体 282 | captcha.setFont(Captcha.FONT_1); 283 | 284 | // 设置系统字体 285 | captcha.setFont(new Font("楷体", Font.PLAIN, 28)); 286 | ``` 287 | 288 | ### 5.4.输出base64编码 289 | ``` 290 | SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5); 291 | specCaptcha.toBase64(); 292 | 293 | // 如果不想要base64的头部data:image/png;base64, 294 | specCaptcha.toBase64(""); // 加一个空的参数即可 295 | ``` 296 | 297 | ### 5.5.输出到文件 298 | ``` 299 | FileOutputStream outputStream = new FileOutputStream(new File("C:/captcha.png")) 300 | SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5); 301 | specCaptcha.out(outputStream); 302 | ``` 303 | 304 | --- 305 | 306 | ## 6.前后端分离项目的使用 307 | 308 |  前后端分离项目建议不要存储在session中,存储在redis中,redis存储需要一个key,key一同返回给前端用于验证输入: 309 | ```java 310 | @Controller 311 | public class CaptchaController { 312 | @Autowired 313 | private RedisUtil redisUtil; 314 | 315 | @ResponseBody 316 | @RequestMapping("/captcha") 317 | public JsonResult captcha(HttpServletRequest request, HttpServletResponse response) throws Exception { 318 | SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5); 319 | String verCode = specCaptcha.text().toLowerCase(); 320 | String key = UUID.randomUUID().toString(); 321 | // 存入redis并设置过期时间为30分钟 322 | redisUtil.setEx(key, verCode, 30, TimeUnit.MINUTES); 323 | // 将key和base64返回给前端 324 | return JsonResult.ok().put("key", key).put("image", specCaptcha.toBase64()); 325 | } 326 | 327 | @ResponseBody 328 | @PostMapping("/login") 329 | public JsonResult login(String username,String password,String verCode,String verKey){ 330 | // 获取redis中的验证码 331 | String redisCode = redisUtil.get(verKey); 332 | // 判断验证码 333 | if (verCode==null || !redisCode.equals(verCode.trim().toLowerCase())) { 334 | return JsonResult.error("验证码不正确"); 335 | } 336 | } 337 | } 338 | ``` 339 | 前端使用ajax获取验证码: 340 | ```html 341 | 342 | 343 | 361 | ``` 362 | 363 | > RedisUtil到这里获取[https://gitee.com/whvse/RedisUtil](https://gitee.com/whvse/RedisUtil) 364 | 365 | --- 366 | 367 | ## 7.自定义效果 368 | 369 |  继承`Captcha`实现`out`方法,中文验证码可继承`ChineseCaptchaAbstract`,算术验证码可继承`ArithmeticCaptchaAbstract`。 370 | 371 | --- 372 | 373 | ## 8.更新日志 374 | 375 | - **2019-08-23 (v1.6.2)** 376 | - 增加10种漂亮的内置字体,不依赖系统字体 377 | 378 | - 增加算术验证码,运算位数可自由配置 379 | - 增加输出base64编码的功能 380 | - 增加贝塞尔曲线作为干扰线 381 | 382 | - **2018-08-09 (v1.5.0)** 383 | - 增加纯大写字母、纯小写字母、数字和大写字母配置 384 | 385 | - 增加中文验证码、中文gif验证码 386 | - 增加抗锯齿效果,优化文字颜色 387 | - 增加CaptchaUtil便于Web项目使用 388 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.github.whvcse 6 | 7 | easy-captcha 8 | 1.6.2 9 | 10 | 11 | 12 | org.apache.maven.plugins 13 | maven-compiler-plugin 14 | 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | jar 22 | 23 | EasyCaptcha 24 | Java web graphics verification code, support gif verification code. 25 | https://github.com/whvcse/EasyCaptcha 26 | 27 | 28 | 29 | The Apache Software License, Version 2.0 30 | http://www.apache.org/licenses/LICENSE-2.0.txt 31 | 32 | 33 | 34 | 35 | whvcse 36 | whvcse@foxmail.com 37 | 38 | 39 | 40 | scm:git@github.com:whvcse/EasyCaptcha.git 41 | scm:git@github.com:whvcse/EasyCaptcha.git 42 | https://github.com/whvcse/EasyCaptcha 43 | 44 | 45 | 46 | UTF-8 47 | UTF-8 48 | 1.8 49 | 50 | 51 | 52 | 53 | 54 | javax.servlet 55 | javax.servlet-api 56 | 3.1.0 57 | provided 58 | 59 | 60 | junit 61 | junit 62 | 4.13.1 63 | test 64 | 65 | 66 | 67 | 68 | 69 | release 70 | 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-source-plugin 76 | 2.2.1 77 | 78 | 79 | package 80 | 81 | jar-no-fork 82 | 83 | 84 | 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-javadoc-plugin 90 | 2.9.1 91 | 92 | 93 | package 94 | 95 | jar 96 | 97 | 98 | 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-gpg-plugin 104 | 1.5 105 | 106 | 107 | verify 108 | 109 | sign 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | oss 119 | https://oss.sonatype.org/content/repositories/snapshots/ 120 | 121 | 122 | oss 123 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/ArithmeticCaptcha.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha; 2 | 3 | import com.wf.captcha.base.ArithmeticCaptchaAbstract; 4 | 5 | import javax.imageio.ImageIO; 6 | import java.awt.*; 7 | import java.awt.image.BufferedImage; 8 | import java.io.IOException; 9 | import java.io.OutputStream; 10 | 11 | /** 12 | * png格式验证码 13 | * Created by 王帆 on 2018-07-27 上午 10:08. 14 | */ 15 | public class ArithmeticCaptcha extends ArithmeticCaptchaAbstract { 16 | 17 | public ArithmeticCaptcha() { 18 | } 19 | 20 | public ArithmeticCaptcha(int width, int height) { 21 | this(); 22 | setWidth(width); 23 | setHeight(height); 24 | } 25 | 26 | public ArithmeticCaptcha(int width, int height, int len) { 27 | this(width, height); 28 | setLen(len); 29 | } 30 | 31 | public ArithmeticCaptcha(int width, int height, int len, Font font) { 32 | this(width, height, len); 33 | setFont(font); 34 | } 35 | 36 | /** 37 | * 生成验证码 38 | * 39 | * @param out 输出流 40 | * @return 是否成功 41 | */ 42 | @Override 43 | public boolean out(OutputStream out) { 44 | checkAlpha(); 45 | return graphicsImage(getArithmeticString().toCharArray(), out); 46 | } 47 | 48 | @Override 49 | public String toBase64() { 50 | return toBase64("data:image/png;base64,"); 51 | } 52 | 53 | /** 54 | * 生成验证码图形 55 | * 56 | * @param strs 验证码 57 | * @param out 输出流 58 | * @return boolean 59 | */ 60 | private boolean graphicsImage(char[] strs, OutputStream out) { 61 | try { 62 | BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 63 | Graphics2D g2d = (Graphics2D) bi.getGraphics(); 64 | // 填充背景 65 | g2d.setColor(Color.WHITE); 66 | g2d.fillRect(0, 0, width, height); 67 | // 抗锯齿 68 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 69 | // 画干扰圆 70 | drawOval(2, g2d); 71 | // 画字符串 72 | g2d.setFont(getFont()); 73 | FontMetrics fontMetrics = g2d.getFontMetrics(); 74 | int fW = width / strs.length; // 每一个字符所占的宽度 75 | int fSp = (fW - (int) fontMetrics.getStringBounds("8", g2d).getWidth()) / 2; // 字符的左右边距 76 | for (int i = 0; i < strs.length; i++) { 77 | g2d.setColor(color()); 78 | int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(strs[i]), g2d).getHeight()) >> 1); // 文字的纵坐标 79 | g2d.drawString(String.valueOf(strs[i]), i * fW + fSp + 3, fY - 3); 80 | } 81 | g2d.dispose(); 82 | ImageIO.write(bi, "png", out); 83 | out.flush(); 84 | return true; 85 | } catch (IOException e) { 86 | e.printStackTrace(); 87 | } finally { 88 | try { 89 | out.close(); 90 | } catch (IOException e) { 91 | e.printStackTrace(); 92 | } 93 | } 94 | return false; 95 | } 96 | } -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/ChineseCaptcha.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha; 2 | 3 | import com.wf.captcha.base.ChineseCaptchaAbstract; 4 | 5 | import javax.imageio.ImageIO; 6 | import java.awt.*; 7 | import java.awt.image.BufferedImage; 8 | import java.io.IOException; 9 | import java.io.OutputStream; 10 | 11 | public class ChineseCaptcha extends ChineseCaptchaAbstract { 12 | 13 | public ChineseCaptcha() { 14 | super(); 15 | } 16 | 17 | public ChineseCaptcha(int width, int height) { 18 | this(); 19 | setWidth(width); 20 | setHeight(height); 21 | } 22 | 23 | public ChineseCaptcha(int width, int height, int len) { 24 | this(width, height); 25 | setLen(len); 26 | } 27 | 28 | public ChineseCaptcha(int width, int height, int len, Font font) { 29 | this(width, height, len); 30 | setFont(font); 31 | } 32 | 33 | /** 34 | * 生成验证码 35 | * 36 | * @param out 输出流 37 | * @return 是否成功 38 | */ 39 | @Override 40 | public boolean out(OutputStream out) { 41 | return graphicsImage(textChar(), out); 42 | } 43 | 44 | @Override 45 | public String toBase64() { 46 | return toBase64("data:image/png;base64,"); 47 | } 48 | 49 | /** 50 | * 生成验证码图形 51 | * 52 | * @param strs 验证码 53 | * @param out 输出流 54 | * @return boolean 55 | */ 56 | private boolean graphicsImage(char[] strs, OutputStream out) { 57 | try { 58 | BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 59 | Graphics2D g2d = (Graphics2D) bi.getGraphics(); 60 | // 填充背景 61 | g2d.setColor(Color.WHITE); 62 | g2d.fillRect(0, 0, width, height); 63 | // 抗锯齿 64 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 65 | // 画干扰圆 66 | drawOval(3, g2d); 67 | // 画干扰线 68 | g2d.setStroke(new BasicStroke(1.2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); 69 | drawBesselLine(1, g2d); 70 | // 画字符串 71 | g2d.setFont(getFont()); 72 | FontMetrics fontMetrics = g2d.getFontMetrics(); 73 | int fW = width / strs.length; // 每一个字符所占的宽度 74 | int fSp = (fW - (int) fontMetrics.getStringBounds("王", g2d).getWidth()) / 2; // 字符的左右边距 75 | for (int i = 0; i < strs.length; i++) { 76 | g2d.setColor(color()); 77 | int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(strs[i]), g2d).getHeight()) >> 1); // 文字的纵坐标 78 | g2d.drawString(String.valueOf(strs[i]), i * fW + fSp + 3, fY - 3); 79 | } 80 | g2d.dispose(); 81 | ImageIO.write(bi, "png", out); 82 | out.flush(); 83 | return true; 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | } finally { 87 | try { 88 | out.close(); 89 | } catch (IOException e) { 90 | e.printStackTrace(); 91 | } 92 | } 93 | return false; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/ChineseGifCaptcha.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha; 2 | 3 | import com.wf.captcha.base.ChineseCaptchaAbstract; 4 | import com.wf.captcha.utils.GifEncoder; 5 | 6 | import java.awt.*; 7 | import java.awt.geom.CubicCurve2D; 8 | import java.awt.image.BufferedImage; 9 | import java.io.IOException; 10 | import java.io.OutputStream; 11 | 12 | public class ChineseGifCaptcha extends ChineseCaptchaAbstract { 13 | 14 | public ChineseGifCaptcha() { 15 | } 16 | 17 | public ChineseGifCaptcha(int width, int height) { 18 | setWidth(width); 19 | setHeight(height); 20 | } 21 | 22 | public ChineseGifCaptcha(int width, int height, int len) { 23 | this(width, height); 24 | setLen(len); 25 | } 26 | 27 | public ChineseGifCaptcha(int width, int height, int len, Font font) { 28 | this(width, height, len); 29 | setFont(font); 30 | } 31 | 32 | @Override 33 | public boolean out(OutputStream os) { 34 | try { 35 | char[] strs = textChar(); // 获取验证码数组 36 | // 随机生成每个文字的颜色 37 | Color fontColor[] = new Color[len]; 38 | for (int i = 0; i < len; i++) { 39 | fontColor[i] = color(); 40 | } 41 | // 随机生成贝塞尔曲线参数 42 | int x1 = 5, y1 = num(5, height / 2); 43 | int x2 = width - 5, y2 = num(height / 2, height - 5); 44 | int ctrlx = num(width / 4, width / 4 * 3), ctrly = num(5, height - 5); 45 | if (num(2) == 0) { 46 | int ty = y1; 47 | y1 = y2; 48 | y2 = ty; 49 | } 50 | int ctrlx1 = num(width / 4, width / 4 * 3), ctrly1 = num(5, height - 5); 51 | int[][] besselXY = new int[][]{{x1, y1}, {ctrlx, ctrly}, {ctrlx1, ctrly1}, {x2, y2}}; 52 | // 开始画gif每一帧 53 | GifEncoder gifEncoder = new GifEncoder(); 54 | gifEncoder.setQuality(180); 55 | gifEncoder.setDelay(100); 56 | gifEncoder.setRepeat(0); 57 | gifEncoder.start(os); 58 | for (int i = 0; i < len; i++) { 59 | BufferedImage frame = graphicsImage(fontColor, strs, i, besselXY); 60 | gifEncoder.addFrame(frame); 61 | frame.flush(); 62 | } 63 | gifEncoder.finish(); 64 | return true; 65 | } catch (Exception e) { 66 | e.printStackTrace(); 67 | } finally { 68 | try { 69 | os.close(); 70 | } catch (IOException e) { 71 | e.printStackTrace(); 72 | } 73 | } 74 | return false; 75 | } 76 | 77 | @Override 78 | public String toBase64() { 79 | return toBase64("data:image/gif;base64,"); 80 | } 81 | 82 | /** 83 | * 画随机码图 84 | * 85 | * @param fontColor 随机字体颜色 86 | * @param strs 字符数组 87 | * @param flag 透明度 88 | * @param besselXY 干扰线参数 89 | * @return BufferedImage 90 | */ 91 | private BufferedImage graphicsImage(Color[] fontColor, char[] strs, int flag, int[][] besselXY) { 92 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 93 | Graphics2D g2d = (Graphics2D) image.getGraphics(); 94 | // 填充背景颜色 95 | g2d.setColor(Color.WHITE); 96 | g2d.fillRect(0, 0, width, height); 97 | // 抗锯齿 98 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 99 | // 画干扰圆圈 100 | g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f * num(10))); // 设置透明度 101 | drawOval(2, g2d); 102 | // 画干扰线 103 | g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f)); // 设置透明度 104 | g2d.setStroke(new BasicStroke(1.2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); 105 | g2d.setColor(fontColor[0]); 106 | CubicCurve2D shape = new CubicCurve2D.Double(besselXY[0][0], besselXY[0][1], besselXY[1][0], besselXY[1][1], besselXY[2][0], besselXY[2][1], besselXY[3][0], besselXY[3][1]); 107 | g2d.draw(shape); 108 | // 画验证码 109 | g2d.setFont(getFont()); 110 | FontMetrics fontMetrics = g2d.getFontMetrics(); 111 | int fW = width / strs.length; // 每一个字符所占的宽度 112 | int fSp = (fW - (int) fontMetrics.getStringBounds("W", g2d).getWidth()) / 2; // 字符的左右边距 113 | for (int i = 0; i < strs.length; i++) { 114 | // 设置透明度 115 | AlphaComposite ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getAlpha(flag, i)); 116 | g2d.setComposite(ac3); 117 | g2d.setColor(fontColor[i]); 118 | int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(strs[i]), g2d).getHeight()) >> 1); // 文字的纵坐标 119 | g2d.drawString(String.valueOf(strs[i]), i * fW + fSp - 3, fY - 3); 120 | } 121 | g2d.dispose(); 122 | return image; 123 | } 124 | 125 | /** 126 | * 获取透明度,从0到1,自动计算步长 127 | * 128 | * @param i 129 | * @param j 130 | * @return 透明度 131 | */ 132 | private float getAlpha(int i, int j) { 133 | int num = i + j; 134 | float r = (float) 1 / (len - 1); 135 | float s = len * r; 136 | return num >= len ? (num * r - s) : num * r; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/GifCaptcha.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha; 2 | 3 | import com.wf.captcha.base.Captcha; 4 | import com.wf.captcha.utils.GifEncoder; 5 | 6 | import java.awt.*; 7 | import java.awt.geom.CubicCurve2D; 8 | import java.awt.geom.QuadCurve2D; 9 | import java.awt.geom.Rectangle2D; 10 | import java.awt.image.BufferedImage; 11 | import java.io.IOException; 12 | import java.io.OutputStream; 13 | 14 | /** 15 | * Gif验证码类 16 | * Created by 王帆 on 2018-07-27 上午 10:08. 17 | */ 18 | public class GifCaptcha extends Captcha { 19 | 20 | public GifCaptcha() { 21 | } 22 | 23 | public GifCaptcha(int width, int height) { 24 | setWidth(width); 25 | setHeight(height); 26 | } 27 | 28 | public GifCaptcha(int width, int height, int len) { 29 | this(width, height); 30 | setLen(len); 31 | } 32 | 33 | public GifCaptcha(int width, int height, int len, Font font) { 34 | this(width, height, len); 35 | setFont(font); 36 | } 37 | 38 | @Override 39 | public boolean out(OutputStream os) { 40 | try { 41 | char[] strs = textChar(); // 获取验证码数组 42 | // 随机生成每个文字的颜色 43 | Color fontColor[] = new Color[len]; 44 | for (int i = 0; i < len; i++) { 45 | fontColor[i] = color(); 46 | } 47 | // 随机生成贝塞尔曲线参数 48 | int x1 = 5, y1 = num(5, height / 2); 49 | int x2 = width - 5, y2 = num(height / 2, height - 5); 50 | int ctrlx = num(width / 4, width / 4 * 3), ctrly = num(5, height - 5); 51 | if (num(2) == 0) { 52 | int ty = y1; 53 | y1 = y2; 54 | y2 = ty; 55 | } 56 | int ctrlx1 = num(width / 4, width / 4 * 3), ctrly1 = num(5, height - 5); 57 | int[][] besselXY = new int[][]{{x1, y1}, {ctrlx, ctrly}, {ctrlx1, ctrly1}, {x2, y2}}; 58 | // 开始画gif每一帧 59 | GifEncoder gifEncoder = new GifEncoder(); 60 | gifEncoder.setQuality(180); 61 | gifEncoder.setDelay(100); 62 | gifEncoder.setRepeat(0); 63 | gifEncoder.start(os); 64 | for (int i = 0; i < len; i++) { 65 | BufferedImage frame = graphicsImage(fontColor, strs, i, besselXY); 66 | gifEncoder.addFrame(frame); 67 | frame.flush(); 68 | } 69 | gifEncoder.finish(); 70 | return true; 71 | } catch (Exception e) { 72 | e.printStackTrace(); 73 | } finally { 74 | try { 75 | os.close(); 76 | } catch (IOException e) { 77 | e.printStackTrace(); 78 | } 79 | } 80 | return false; 81 | } 82 | 83 | @Override 84 | public String toBase64() { 85 | return toBase64("data:image/gif;base64,"); 86 | } 87 | 88 | /** 89 | * 画随机码图 90 | * 91 | * @param fontColor 随机字体颜色 92 | * @param strs 字符数组 93 | * @param flag 透明度 94 | * @param besselXY 干扰线参数 95 | * @return BufferedImage 96 | */ 97 | private BufferedImage graphicsImage(Color[] fontColor, char[] strs, int flag, int[][] besselXY) { 98 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 99 | Graphics2D g2d = (Graphics2D) image.getGraphics(); 100 | // 填充背景颜色 101 | g2d.setColor(Color.WHITE); 102 | g2d.fillRect(0, 0, width, height); 103 | // 抗锯齿 104 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 105 | // 画干扰圆圈 106 | g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f * num(10))); // 设置透明度 107 | drawOval(2, g2d); 108 | // 画干扰线 109 | g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f)); // 设置透明度 110 | g2d.setStroke(new BasicStroke(1.2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); 111 | g2d.setColor(fontColor[0]); 112 | CubicCurve2D shape = new CubicCurve2D.Double(besselXY[0][0], besselXY[0][1], besselXY[1][0], besselXY[1][1], besselXY[2][0], besselXY[2][1], besselXY[3][0], besselXY[3][1]); 113 | g2d.draw(shape); 114 | // 画验证码 115 | g2d.setFont(getFont()); 116 | FontMetrics fontMetrics = g2d.getFontMetrics(); 117 | int fW = width / strs.length; // 每一个字符所占的宽度 118 | int fSp = (fW - (int) fontMetrics.getStringBounds("W", g2d).getWidth()) / 2; // 字符的左右边距 119 | for (int i = 0; i < strs.length; i++) { 120 | // 设置透明度 121 | AlphaComposite ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getAlpha(flag, i)); 122 | g2d.setComposite(ac3); 123 | g2d.setColor(fontColor[i]); 124 | int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(strs[i]), g2d).getHeight()) >> 1); // 文字的纵坐标 125 | g2d.drawString(String.valueOf(strs[i]), i * fW + fSp + 3, fY - 3); 126 | } 127 | g2d.dispose(); 128 | return image; 129 | } 130 | 131 | /** 132 | * 获取透明度,从0到1,自动计算步长 133 | * 134 | * @param i 135 | * @param j 136 | * @return 透明度 137 | */ 138 | private float getAlpha(int i, int j) { 139 | int num = i + j; 140 | float r = (float) 1 / (len - 1); 141 | float s = len * r; 142 | return num >= len ? (num * r - s) : num * r; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/SpecCaptcha.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha; 2 | 3 | import com.wf.captcha.base.Captcha; 4 | 5 | import java.awt.*; 6 | import java.awt.image.BufferedImage; 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | 10 | import javax.imageio.ImageIO; 11 | 12 | /** 13 | * png格式验证码 14 | * Created by 王帆 on 2018-07-27 上午 10:08. 15 | */ 16 | public class SpecCaptcha extends Captcha { 17 | 18 | public SpecCaptcha() { 19 | } 20 | 21 | public SpecCaptcha(int width, int height) { 22 | this(); 23 | setWidth(width); 24 | setHeight(height); 25 | } 26 | 27 | public SpecCaptcha(int width, int height, int len) { 28 | this(width, height); 29 | setLen(len); 30 | } 31 | 32 | public SpecCaptcha(int width, int height, int len, Font font) { 33 | this(width, height, len); 34 | setFont(font); 35 | } 36 | 37 | /** 38 | * 生成验证码 39 | * 40 | * @param out 输出流 41 | * @return 是否成功 42 | */ 43 | @Override 44 | public boolean out(OutputStream out) { 45 | return graphicsImage(textChar(), out); 46 | } 47 | 48 | @Override 49 | public String toBase64() { 50 | return toBase64("data:image/png;base64,"); 51 | } 52 | 53 | /** 54 | * 生成验证码图形 55 | * 56 | * @param strs 验证码 57 | * @param out 输出流 58 | * @return boolean 59 | */ 60 | private boolean graphicsImage(char[] strs, OutputStream out) { 61 | try { 62 | BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 63 | Graphics2D g2d = (Graphics2D) bi.getGraphics(); 64 | // 填充背景 65 | g2d.setColor(Color.WHITE); 66 | g2d.fillRect(0, 0, width, height); 67 | // 抗锯齿 68 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 69 | // 画干扰圆 70 | drawOval(2, g2d); 71 | // 画干扰线 72 | g2d.setStroke(new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); 73 | drawBesselLine(1, g2d); 74 | // 画字符串 75 | g2d.setFont(getFont()); 76 | FontMetrics fontMetrics = g2d.getFontMetrics(); 77 | int fW = width / strs.length; // 每一个字符所占的宽度 78 | int fSp = (fW - (int) fontMetrics.getStringBounds("W", g2d).getWidth()) / 2; // 字符的左右边距 79 | for (int i = 0; i < strs.length; i++) { 80 | g2d.setColor(color()); 81 | int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(strs[i]), g2d).getHeight()) >> 1); // 文字的纵坐标 82 | g2d.drawString(String.valueOf(strs[i]), i * fW + fSp + 3, fY - 3); 83 | } 84 | g2d.dispose(); 85 | ImageIO.write(bi, "png", out); 86 | out.flush(); 87 | return true; 88 | } catch (IOException e) { 89 | e.printStackTrace(); 90 | } finally { 91 | try { 92 | out.close(); 93 | } catch (IOException e) { 94 | e.printStackTrace(); 95 | } 96 | } 97 | return false; 98 | } 99 | } -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/base/ArithHelper.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha.base; 2 | 3 | /** 4 | * 字符串计算器辅助类 5 | * @link https://www.cnblogs.com/woider/p/5331391.html 6 | */ 7 | class ArithHelper { 8 | 9 | // 默认除法运算精度 10 | private static final int DEF_DIV_SCALE = 16; 11 | 12 | // 这个类不能实例化 13 | private ArithHelper() { 14 | } 15 | 16 | /** 17 | * 提供精确的加法运算。 18 | * 19 | * @param v1 被加数 20 | * @param v2 加数 21 | * @return 两个参数的和 22 | */ 23 | 24 | public static double add(double v1, double v2) { 25 | java.math.BigDecimal b1 = new java.math.BigDecimal(Double.toString(v1)); 26 | java.math.BigDecimal b2 = new java.math.BigDecimal(Double.toString(v2)); 27 | return b1.add(b2).doubleValue(); 28 | } 29 | 30 | public static double add(String v1, String v2) { 31 | java.math.BigDecimal b1 = new java.math.BigDecimal(v1); 32 | java.math.BigDecimal b2 = new java.math.BigDecimal(v2); 33 | return b1.add(b2).doubleValue(); 34 | } 35 | 36 | /** 37 | * 提供精确的减法运算。 38 | * 39 | * @param v1 被减数 40 | * @param v2 减数 41 | * @return 两个参数的差 42 | */ 43 | 44 | public static double sub(double v1, double v2) { 45 | java.math.BigDecimal b1 = new java.math.BigDecimal(Double.toString(v1)); 46 | java.math.BigDecimal b2 = new java.math.BigDecimal(Double.toString(v2)); 47 | return b1.subtract(b2).doubleValue(); 48 | } 49 | 50 | public static double sub(String v1, String v2) { 51 | java.math.BigDecimal b1 = new java.math.BigDecimal(v1); 52 | java.math.BigDecimal b2 = new java.math.BigDecimal(v2); 53 | return b1.subtract(b2).doubleValue(); 54 | } 55 | 56 | /** 57 | * 提供精确的乘法运算。 58 | * 59 | * @param v1 被乘数 60 | * @param v2 乘数 61 | * @return 两个参数的积 62 | */ 63 | 64 | public static double mul(double v1, double v2) { 65 | java.math.BigDecimal b1 = new java.math.BigDecimal(Double.toString(v1)); 66 | java.math.BigDecimal b2 = new java.math.BigDecimal(Double.toString(v2)); 67 | return b1.multiply(b2).doubleValue(); 68 | } 69 | 70 | public static double mul(String v1, String v2) { 71 | java.math.BigDecimal b1 = new java.math.BigDecimal(v1); 72 | java.math.BigDecimal b2 = new java.math.BigDecimal(v2); 73 | return b1.multiply(b2).doubleValue(); 74 | } 75 | 76 | /** 77 | * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 小数点以后10位,以后的数字四舍五入。 78 | * 79 | * @param v1 被除数 80 | * @param v2 除数 81 | * @return 两个参数的商 82 | */ 83 | 84 | public static double div(double v1, double v2) { 85 | return div(v1, v2, DEF_DIV_SCALE); 86 | } 87 | 88 | public static double div(String v1, String v2) { 89 | java.math.BigDecimal b1 = new java.math.BigDecimal(v1); 90 | java.math.BigDecimal b2 = new java.math.BigDecimal(v2); 91 | return b1.divide(b2, DEF_DIV_SCALE, java.math.BigDecimal.ROUND_HALF_UP).doubleValue(); 92 | } 93 | 94 | /** 95 | * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 定精度,以后的数字四舍五入。 96 | * 97 | * @param v1 被除数 98 | * @param v2 除数 99 | * @param scale 表示表示需要精确到小数点以后几位。 100 | * @return 两个参数的商 101 | */ 102 | 103 | public static double div(double v1, double v2, int scale) { 104 | if (scale < 0) { 105 | throw new IllegalArgumentException("The scale must be a positive integer or zero"); 106 | } 107 | java.math.BigDecimal b1 = new java.math.BigDecimal(Double.toString(v1)); 108 | java.math.BigDecimal b2 = new java.math.BigDecimal(Double.toString(v2)); 109 | return b1.divide(b2, scale, java.math.BigDecimal.ROUND_HALF_UP).doubleValue(); 110 | } 111 | 112 | /** 113 | * 提供精确的小数位四舍五入处理。 114 | * 115 | * @param v 需要四舍五入的数字 116 | * @param scale 小数点后保留几位 117 | * @return 四舍五入后的结果 118 | */ 119 | 120 | public static double round(double v, int scale) { 121 | if (scale < 0) { 122 | throw new IllegalArgumentException("The scale must be a positive integer or zero"); 123 | } 124 | java.math.BigDecimal b = new java.math.BigDecimal(Double.toString(v)); 125 | java.math.BigDecimal one = new java.math.BigDecimal("1"); 126 | return b.divide(one, scale, java.math.BigDecimal.ROUND_HALF_UP).doubleValue(); 127 | } 128 | 129 | public static double round(String v, int scale) { 130 | if (scale < 0) { 131 | throw new IllegalArgumentException("The scale must be a positive integer or zero"); 132 | } 133 | java.math.BigDecimal b = new java.math.BigDecimal(v); 134 | java.math.BigDecimal one = new java.math.BigDecimal("1"); 135 | return b.divide(one, scale, java.math.BigDecimal.ROUND_HALF_UP).doubleValue(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/base/ArithmeticCaptchaAbstract.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha.base; 2 | 3 | //import javax.script.ScriptEngine; 4 | //import javax.script.ScriptEngineManager; 5 | //import javax.script.ScriptException; 6 | 7 | /** 8 | * 算术验证码抽象类 9 | * Created by 王帆 on 2019-08-23 上午 10:08. 10 | */ 11 | public abstract class ArithmeticCaptchaAbstract extends Captcha { 12 | private String arithmeticString; // 计算公式 13 | 14 | public ArithmeticCaptchaAbstract() { 15 | setLen(2); 16 | } 17 | 18 | /** 19 | * 生成随机验证码 20 | * 21 | * @return 验证码字符数组 22 | */ 23 | @Override 24 | protected char[] alphas() { 25 | StringBuilder sb = new StringBuilder(); 26 | for (int i = 0; i < len; i++) { 27 | sb.append(num(10)); 28 | if (i < len - 1) { 29 | int type = num(1, 4); 30 | if (type == 1) { 31 | sb.append("+"); 32 | } else if (type == 2) { 33 | sb.append("-"); 34 | } else if (type == 3) { 35 | sb.append("x"); 36 | } 37 | } 38 | } 39 | 40 | // ScriptEngineManager manager = new ScriptEngineManager(); 41 | // ScriptEngine engine = manager.getEngineByName("javascript"); 42 | // try { 43 | // chars = String.valueOf(engine.eval(sb.toString().replaceAll("x", "*"))); 44 | // } catch (ScriptException e) { 45 | // e.printStackTrace(); 46 | // } 47 | int result = (int) Calculator.conversion(sb.toString().replaceAll("x", "*")); 48 | this.chars = String.valueOf(result); 49 | 50 | sb.append("=?"); 51 | arithmeticString = sb.toString(); 52 | return chars.toCharArray(); 53 | } 54 | 55 | public String getArithmeticString() { 56 | checkAlpha(); 57 | return arithmeticString; 58 | } 59 | 60 | public void setArithmeticString(String arithmeticString) { 61 | this.arithmeticString = arithmeticString; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/base/Calculator.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha.base; 2 | 3 | import java.util.Collections; 4 | import java.util.Stack; 5 | 6 | 7 | /** 8 | * 字符串计算器 9 | * @link https://www.cnblogs.com/woider/p/5331391.html 10 | */ 11 | public class Calculator { 12 | private final Stack postfixStack = new Stack();// 后缀式栈 13 | private final Stack opStack = new Stack();// 运算符栈 14 | private final int[] operatPriority = new int[]{0, 3, 2, 1, -1, 1, 0, 2};// 运用运算符ASCII码-40做索引的运算符优先级 15 | 16 | public static double conversion(String expression) { 17 | double result = 0; 18 | Calculator cal = new Calculator(); 19 | try { 20 | expression = transform(expression); 21 | result = cal.calculate(expression); 22 | } catch (Exception e) { 23 | // e.printStackTrace(); 24 | // 运算错误返回NaN 25 | return 0.0 / 0.0; 26 | } 27 | // return new String().valueOf(result); 28 | return result; 29 | } 30 | 31 | /** 32 | * 将表达式中负数的符号更改 33 | * 34 | * @param expression 例如-2+-1*(-3E-2)-(-1) 被转为 ~2+~1*(~3E~2)-(~1) 35 | * @return 36 | */ 37 | private static String transform(String expression) { 38 | char[] arr = expression.toCharArray(); 39 | for (int i = 0; i < arr.length; i++) { 40 | if (arr[i] == '-') { 41 | if (i == 0) { 42 | arr[i] = '~'; 43 | } else { 44 | char c = arr[i - 1]; 45 | if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == 'E' || c == 'e') { 46 | arr[i] = '~'; 47 | } 48 | } 49 | } 50 | } 51 | if (arr[0] == '~' || arr[1] == '(') { 52 | arr[0] = '-'; 53 | return "0" + new String(arr); 54 | } else { 55 | return new String(arr); 56 | } 57 | } 58 | 59 | /** 60 | * 按照给定的表达式计算 61 | * 62 | * @param expression 要计算的表达式例如:5+12*(3+5)/7 63 | * @return 64 | */ 65 | public double calculate(String expression) { 66 | Stack resultStack = new Stack(); 67 | prepare(expression); 68 | Collections.reverse(postfixStack);// 将后缀式栈反转 69 | String firstValue, secondValue, currentValue;// 参与计算的第一个值,第二个值和算术运算符 70 | while (!postfixStack.isEmpty()) { 71 | currentValue = postfixStack.pop(); 72 | if (!isOperator(currentValue.charAt(0))) {// 如果不是运算符则存入操作数栈中 73 | currentValue = currentValue.replace("~", "-"); 74 | resultStack.push(currentValue); 75 | } else {// 如果是运算符则从操作数栈中取两个值和该数值一起参与运算 76 | secondValue = resultStack.pop(); 77 | firstValue = resultStack.pop(); 78 | 79 | // 将负数标记符改为负号 80 | firstValue = firstValue.replace("~", "-"); 81 | secondValue = secondValue.replace("~", "-"); 82 | 83 | String tempResult = calculate(firstValue, secondValue, currentValue.charAt(0)); 84 | resultStack.push(tempResult); 85 | } 86 | } 87 | return Double.valueOf(resultStack.pop()); 88 | } 89 | 90 | /** 91 | * 数据准备阶段将表达式转换成为后缀式栈 92 | * 93 | * @param expression 94 | */ 95 | private void prepare(String expression) { 96 | opStack.push(',');// 运算符放入栈底元素逗号,此符号优先级最低 97 | char[] arr = expression.toCharArray(); 98 | int currentIndex = 0;// 当前字符的位置 99 | int count = 0;// 上次算术运算符到本次算术运算符的字符的长度便于或者之间的数值 100 | char currentOp, peekOp;// 当前操作符和栈顶操作符 101 | for (int i = 0; i < arr.length; i++) { 102 | currentOp = arr[i]; 103 | if (isOperator(currentOp)) {// 如果当前字符是运算符 104 | if (count > 0) { 105 | postfixStack.push(new String(arr, currentIndex, count));// 取两个运算符之间的数字 106 | } 107 | peekOp = opStack.peek(); 108 | if (currentOp == ')') {// 遇到反括号则将运算符栈中的元素移除到后缀式栈中直到遇到左括号 109 | while (opStack.peek() != '(') { 110 | postfixStack.push(String.valueOf(opStack.pop())); 111 | } 112 | opStack.pop(); 113 | } else { 114 | while (currentOp != '(' && peekOp != ',' && compare(currentOp, peekOp)) { 115 | postfixStack.push(String.valueOf(opStack.pop())); 116 | peekOp = opStack.peek(); 117 | } 118 | opStack.push(currentOp); 119 | } 120 | count = 0; 121 | currentIndex = i + 1; 122 | } else { 123 | count++; 124 | } 125 | } 126 | if (count > 1 || (count == 1 && !isOperator(arr[currentIndex]))) {// 最后一个字符不是括号或者其他运算符的则加入后缀式栈中 127 | postfixStack.push(new String(arr, currentIndex, count)); 128 | } 129 | 130 | while (opStack.peek() != ',') { 131 | postfixStack.push(String.valueOf(opStack.pop()));// 将操作符栈中的剩余的元素添加到后缀式栈中 132 | } 133 | } 134 | 135 | /** 136 | * 判断是否为算术符号 137 | * 138 | * @param c 139 | * @return 140 | */ 141 | private boolean isOperator(char c) { 142 | return c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')'; 143 | } 144 | 145 | /** 146 | * 利用ASCII码-40做下标去算术符号优先级 147 | * 148 | * @param cur 149 | * @param peek 150 | * @return 151 | */ 152 | public boolean compare(char cur, char peek) {// 如果是peek优先级高于cur,返回true,默认都是peek优先级要低 153 | boolean result = false; 154 | if (operatPriority[(peek) - 40] >= operatPriority[(cur) - 40]) { 155 | result = true; 156 | } 157 | return result; 158 | } 159 | 160 | /** 161 | * 按照给定的算术运算符做计算 162 | * 163 | * @param firstValue 164 | * @param secondValue 165 | * @param currentOp 166 | * @return 167 | */ 168 | private String calculate(String firstValue, String secondValue, char currentOp) { 169 | String result = ""; 170 | switch (currentOp) { 171 | case '+': 172 | result = String.valueOf(ArithHelper.add(firstValue, secondValue)); 173 | break; 174 | case '-': 175 | result = String.valueOf(ArithHelper.sub(firstValue, secondValue)); 176 | break; 177 | case '*': 178 | result = String.valueOf(ArithHelper.mul(firstValue, secondValue)); 179 | break; 180 | case '/': 181 | result = String.valueOf(ArithHelper.div(firstValue, secondValue)); 182 | break; 183 | } 184 | return result; 185 | } 186 | } 187 | 188 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/base/Captcha.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha.base; 2 | 3 | import java.awt.*; 4 | import java.awt.geom.CubicCurve2D; 5 | import java.awt.geom.QuadCurve2D; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.util.Base64; 10 | 11 | /** 12 | * 验证码抽象类 13 | * Created by 王帆 on 2018-07-27 上午 10:08. 14 | */ 15 | public abstract class Captcha extends Randoms { 16 | // 常用颜色 17 | public static final int[][] COLOR = {{0, 135, 255}, {51, 153, 51}, {255, 102, 102}, {255, 153, 0}, {153, 102, 0}, {153, 102, 153}, {51, 153, 153}, {102, 102, 255}, {0, 102, 204}, {204, 51, 51}, {0, 153, 204}, {0, 51, 102}}; 18 | // 验证码文本类型 19 | public static final int TYPE_DEFAULT = 1; // 字母数字混合 20 | public static final int TYPE_ONLY_NUMBER = 2; // 纯数字 21 | public static final int TYPE_ONLY_CHAR = 3; // 纯字母 22 | public static final int TYPE_ONLY_UPPER = 4; // 纯大写字母 23 | public static final int TYPE_ONLY_LOWER = 5; // 纯小写字母 24 | public static final int TYPE_NUM_AND_UPPER = 6; // 数字大写字母 25 | // 内置字体 26 | public static final int FONT_1 = 0; 27 | public static final int FONT_2 = 1; 28 | public static final int FONT_3 = 2; 29 | public static final int FONT_4 = 3; 30 | public static final int FONT_5 = 4; 31 | public static final int FONT_6 = 5; 32 | public static final int FONT_7 = 6; 33 | public static final int FONT_8 = 7; 34 | public static final int FONT_9 = 8; 35 | public static final int FONT_10 = 9; 36 | private static final String[] FONT_NAMES = new String[]{"actionj.ttf", "epilog.ttf", "fresnel.ttf", "headache.ttf", "lexo.ttf", "prefix.ttf", "progbot.ttf", "ransom.ttf", "robot.ttf", "scandal.ttf"}; 37 | private Font font = null; // 验证码的字体 38 | protected int len = 5; // 验证码随机字符长度 39 | protected int width = 130; // 验证码显示宽度 40 | protected int height = 48; // 验证码显示高度 41 | protected int charType = TYPE_DEFAULT; // 验证码类型 42 | protected String chars = null; // 当前验证码 43 | 44 | /** 45 | * 生成随机验证码 46 | * 47 | * @return 验证码字符数组 48 | */ 49 | protected char[] alphas() { 50 | char[] cs = new char[len]; 51 | for (int i = 0; i < len; i++) { 52 | switch (charType) { 53 | case 2: 54 | cs[i] = alpha(numMaxIndex); 55 | break; 56 | case 3: 57 | cs[i] = alpha(charMinIndex, charMaxIndex); 58 | break; 59 | case 4: 60 | cs[i] = alpha(upperMinIndex, upperMaxIndex); 61 | break; 62 | case 5: 63 | cs[i] = alpha(lowerMinIndex, lowerMaxIndex); 64 | break; 65 | case 6: 66 | cs[i] = alpha(upperMaxIndex); 67 | break; 68 | default: 69 | cs[i] = alpha(); 70 | } 71 | } 72 | chars = new String(cs); 73 | return cs; 74 | } 75 | 76 | /** 77 | * 给定范围获得随机颜色 78 | * 79 | * @param fc 0-255 80 | * @param bc 0-255 81 | * @return 随机颜色 82 | */ 83 | protected Color color(int fc, int bc) { 84 | if (fc > 255) 85 | fc = 255; 86 | if (bc > 255) 87 | bc = 255; 88 | int r = fc + num(bc - fc); 89 | int g = fc + num(bc - fc); 90 | int b = fc + num(bc - fc); 91 | return new Color(r, g, b); 92 | } 93 | 94 | /** 95 | * 获取随机常用颜色 96 | * 97 | * @return 随机颜色 98 | */ 99 | protected Color color() { 100 | int[] color = COLOR[num(COLOR.length)]; 101 | return new Color(color[0], color[1], color[2]); 102 | } 103 | 104 | /** 105 | * 验证码输出,抽象方法,由子类实现 106 | * 107 | * @param os 输出流 108 | * @return 是否成功 109 | */ 110 | public abstract boolean out(OutputStream os); 111 | 112 | /** 113 | * 输出base64编码 114 | * 115 | * @return base64编码字符串 116 | */ 117 | public abstract String toBase64(); 118 | 119 | /** 120 | * 输出base64编码 121 | * 122 | * @param type 编码头 123 | * @return base64编码字符串 124 | */ 125 | public String toBase64(String type) { 126 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 127 | out(outputStream); 128 | return type + Base64.getEncoder().encodeToString(outputStream.toByteArray()); 129 | } 130 | 131 | /** 132 | * 获取当前的验证码 133 | * 134 | * @return 字符串 135 | */ 136 | public String text() { 137 | checkAlpha(); 138 | return chars; 139 | } 140 | 141 | /** 142 | * 获取当前验证码的字符数组 143 | * 144 | * @return 字符数组 145 | */ 146 | public char[] textChar() { 147 | checkAlpha(); 148 | return chars.toCharArray(); 149 | } 150 | 151 | /** 152 | * 检查验证码是否生成,没有则立即生成 153 | */ 154 | public void checkAlpha() { 155 | if (chars == null) { 156 | alphas(); // 生成验证码 157 | } 158 | } 159 | 160 | /** 161 | * 随机画干扰线 162 | * 163 | * @param num 数量 164 | * @param g Graphics2D 165 | */ 166 | public void drawLine(int num, Graphics2D g) { 167 | drawLine(num, null, g); 168 | } 169 | 170 | /** 171 | * 随机画干扰线 172 | * 173 | * @param num 数量 174 | * @param color 颜色 175 | * @param g Graphics2D 176 | */ 177 | public void drawLine(int num, Color color, Graphics2D g) { 178 | for (int i = 0; i < num; i++) { 179 | g.setColor(color == null ? color() : color); 180 | int x1 = num(-10, width - 10); 181 | int y1 = num(5, height - 5); 182 | int x2 = num(10, width + 10); 183 | int y2 = num(2, height - 2); 184 | g.drawLine(x1, y1, x2, y2); 185 | } 186 | } 187 | 188 | /** 189 | * 随机画干扰圆 190 | * 191 | * @param num 数量 192 | * @param g Graphics2D 193 | */ 194 | public void drawOval(int num, Graphics2D g) { 195 | drawOval(num, null, g); 196 | } 197 | 198 | /** 199 | * 随机画干扰圆 200 | * 201 | * @param num 数量 202 | * @param color 颜色 203 | * @param g Graphics2D 204 | */ 205 | public void drawOval(int num, Color color, Graphics2D g) { 206 | for (int i = 0; i < num; i++) { 207 | g.setColor(color == null ? color() : color); 208 | int w = 5 + num(10); 209 | g.drawOval(num(width - 25), num(height - 15), w, w); 210 | } 211 | } 212 | 213 | /** 214 | * 随机画贝塞尔曲线 215 | * 216 | * @param num 数量 217 | * @param g Graphics2D 218 | */ 219 | public void drawBesselLine(int num, Graphics2D g) { 220 | drawBesselLine(num, null, g); 221 | } 222 | 223 | /** 224 | * 随机画贝塞尔曲线 225 | * 226 | * @param num 数量 227 | * @param color 颜色 228 | * @param g Graphics2D 229 | */ 230 | public void drawBesselLine(int num, Color color, Graphics2D g) { 231 | for (int i = 0; i < num; i++) { 232 | g.setColor(color == null ? color() : color); 233 | int x1 = 5, y1 = num(5, height / 2); 234 | int x2 = width - 5, y2 = num(height / 2, height - 5); 235 | int ctrlx = num(width / 4, width / 4 * 3), ctrly = num(5, height - 5); 236 | if (num(2) == 0) { 237 | int ty = y1; 238 | y1 = y2; 239 | y2 = ty; 240 | } 241 | if (num(2) == 0) { // 二阶贝塞尔曲线 242 | QuadCurve2D shape = new QuadCurve2D.Double(); 243 | shape.setCurve(x1, y1, ctrlx, ctrly, x2, y2); 244 | g.draw(shape); 245 | } else { // 三阶贝塞尔曲线 246 | int ctrlx1 = num(width / 4, width / 4 * 3), ctrly1 = num(5, height - 5); 247 | CubicCurve2D shape = new CubicCurve2D.Double(x1, y1, ctrlx, ctrly, ctrlx1, ctrly1, x2, y2); 248 | g.draw(shape); 249 | } 250 | } 251 | } 252 | 253 | public Font getFont() { 254 | if (font == null) { 255 | try { 256 | setFont(FONT_1); 257 | } catch (Exception e) { 258 | setFont(new Font("Arial", Font.BOLD, 32)); 259 | } 260 | } 261 | return font; 262 | } 263 | 264 | public void setFont(Font font) { 265 | this.font = font; 266 | } 267 | 268 | public void setFont(int font) throws IOException, FontFormatException { 269 | setFont(font, 32f); 270 | } 271 | 272 | public void setFont(int font, float size) throws IOException, FontFormatException { 273 | setFont(font, Font.BOLD, size); 274 | } 275 | 276 | public void setFont(int font, int style, float size) throws IOException, FontFormatException { 277 | this.font = Font.createFont(Font.TRUETYPE_FONT, getClass().getResourceAsStream("/" + FONT_NAMES[font])).deriveFont(style, size); 278 | } 279 | 280 | public int getLen() { 281 | return len; 282 | } 283 | 284 | public void setLen(int len) { 285 | this.len = len; 286 | } 287 | 288 | public int getWidth() { 289 | return width; 290 | } 291 | 292 | public void setWidth(int width) { 293 | this.width = width; 294 | } 295 | 296 | public int getHeight() { 297 | return height; 298 | } 299 | 300 | public void setHeight(int height) { 301 | this.height = height; 302 | } 303 | 304 | public int getCharType() { 305 | return charType; 306 | } 307 | 308 | public void setCharType(int charType) { 309 | this.charType = charType; 310 | } 311 | } -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/base/ChineseCaptchaAbstract.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha.base; 2 | 3 | import java.awt.*; 4 | 5 | /** 6 | * 中文验证码抽象类 7 | * Created by 王帆 on 2018-07-27 上午 10:08. 8 | */ 9 | public abstract class ChineseCaptchaAbstract extends Captcha { 10 | // 常用汉字 11 | public static final String DELTA = "\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6"; 12 | 13 | public ChineseCaptchaAbstract() { 14 | setFont(new Font("楷体", Font.PLAIN, 28)); 15 | setLen(4); 16 | } 17 | 18 | /** 19 | * 生成随机验证码 20 | * 21 | * @return 验证码字符数组 22 | */ 23 | @Override 24 | protected char[] alphas() { 25 | char[] cs = new char[len]; 26 | for (int i = 0; i < len; i++) { 27 | cs[i] = alphaHan(); 28 | } 29 | chars = new String(cs); 30 | return cs; 31 | } 32 | 33 | /** 34 | * 返回随机汉字 35 | * 36 | * @return 随机汉字 37 | */ 38 | public static char alphaHan() { 39 | return DELTA.charAt(num(DELTA.length())); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/base/Randoms.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha.base; 2 | 3 | import java.security.SecureRandom; 4 | 5 | /** 6 | * 随机数工具类 7 | * Created by 王帆 on 2018-07-27 上午 10:08. 8 | */ 9 | public class Randoms { 10 | protected static final SecureRandom RANDOM = new SecureRandom(); 11 | // 定义验证码字符.去除了0、O、I、L等容易混淆的字母 12 | public static final char ALPHA[] = {'2', '3', '4', '5', '6', '7', '8', '9', 13 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 14 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 15 | protected static final int numMaxIndex = 8; // 数字的最大索引,不包括最大值 16 | protected static final int charMinIndex = numMaxIndex; // 字符的最小索引,包括最小值 17 | protected static final int charMaxIndex = ALPHA.length; // 字符的最大索引,不包括最大值 18 | protected static final int upperMinIndex = charMinIndex; // 大写字符最小索引 19 | protected static final int upperMaxIndex = upperMinIndex + 23; // 大写字符最大索引 20 | protected static final int lowerMinIndex = upperMaxIndex; // 小写字母最小索引 21 | protected static final int lowerMaxIndex = charMaxIndex; // 小写字母最大索引 22 | 23 | /** 24 | * 产生两个数之间的随机数 25 | * 26 | * @param min 最小值 27 | * @param max 最大值 28 | * @return 随机数 29 | */ 30 | public static int num(int min, int max) { 31 | return min + RANDOM.nextInt(max - min); 32 | } 33 | 34 | /** 35 | * 产生0-num的随机数,不包括num 36 | * 37 | * @param num 最大值 38 | * @return 随机数 39 | */ 40 | public static int num(int num) { 41 | return RANDOM.nextInt(num); 42 | } 43 | 44 | /** 45 | * 返回ALPHA中的随机字符 46 | * 47 | * @return 随机字符 48 | */ 49 | public static char alpha() { 50 | return ALPHA[num(ALPHA.length)]; 51 | } 52 | 53 | /** 54 | * 返回ALPHA中第0位到第num位的随机字符 55 | * 56 | * @param num 到第几位结束 57 | * @return 随机字符 58 | */ 59 | public static char alpha(int num) { 60 | return ALPHA[num(num)]; 61 | } 62 | 63 | /** 64 | * 返回ALPHA中第min位到第max位的随机字符 65 | * 66 | * @param min 从第几位开始 67 | * @param max 到第几位结束 68 | * @return 随机字符 69 | */ 70 | public static char alpha(int min, int max) { 71 | return ALPHA[num(min, max)]; 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/servlet/CaptchaServlet.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha.servlet; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServlet; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import com.wf.captcha.utils.CaptchaUtil; 11 | 12 | /** 13 | * 验证码servlet 14 | * Created by 王帆 on 2018-07-27 上午 10:08. 15 | */ 16 | public class CaptchaServlet extends HttpServlet { 17 | private static final long serialVersionUID = -90304944339413093L; 18 | 19 | public void doGet(HttpServletRequest request, HttpServletResponse response) 20 | throws ServletException, IOException { 21 | CaptchaUtil.out(request, response); 22 | } 23 | 24 | public void doPost(HttpServletRequest request, HttpServletResponse response) 25 | throws ServletException, IOException { 26 | doGet(request, response); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/utils/CaptchaUtil.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha.utils; 2 | 3 | import java.awt.*; 4 | import java.io.IOException; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import com.wf.captcha.base.Captcha; 10 | import com.wf.captcha.SpecCaptcha; 11 | 12 | /** 13 | * 图形验证码工具类 14 | * Created by 王帆 on 2018-07-27 上午 10:08. 15 | */ 16 | public class CaptchaUtil { 17 | private static final String SESSION_KEY = "captcha"; 18 | private static final int DEFAULT_LEN = 4; // 默认长度 19 | private static final int DEFAULT_WIDTH = 130; // 默认宽度 20 | private static final int DEFAULT_HEIGHT = 48; // 默认高度 21 | 22 | /** 23 | * 输出验证码 24 | * 25 | * @param request HttpServletRequest 26 | * @param response HttpServletResponse 27 | * @throws IOException IO异常 28 | */ 29 | public static void out(HttpServletRequest request, HttpServletResponse response) 30 | throws IOException { 31 | out(DEFAULT_LEN, request, response); 32 | } 33 | 34 | /** 35 | * 输出验证码 36 | * 37 | * @param len 长度 38 | * @param request HttpServletRequest 39 | * @param response HttpServletResponse 40 | * @throws IOException IO异常 41 | */ 42 | public static void out(int len, HttpServletRequest request, HttpServletResponse response) 43 | throws IOException { 44 | out(DEFAULT_WIDTH, DEFAULT_HEIGHT, len, request, response); 45 | } 46 | 47 | /** 48 | * 输出验证码 49 | * 50 | * @param width 宽度 51 | * @param height 高度 52 | * @param len 长度 53 | * @param request HttpServletRequest 54 | * @param response HttpServletResponse 55 | * @throws IOException IO异常 56 | */ 57 | public static void out(int width, int height, int len, HttpServletRequest request, HttpServletResponse response) 58 | throws IOException { 59 | out(width, height, len, null, request, response); 60 | } 61 | 62 | /** 63 | * 输出验证码 64 | * 65 | * @param font 字体 66 | * @param request HttpServletRequest 67 | * @param response HttpServletResponse 68 | * @throws IOException IO异常 69 | */ 70 | public static void out(Font font, HttpServletRequest request, HttpServletResponse response) 71 | throws IOException { 72 | out(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_LEN, font, request, response); 73 | } 74 | 75 | /** 76 | * 输出验证码 77 | * 78 | * @param len 长度 79 | * @param font 字体 80 | * @param request HttpServletRequest 81 | * @param response HttpServletResponse 82 | * @throws IOException IO异常 83 | */ 84 | public static void out(int len, Font font, HttpServletRequest request, HttpServletResponse response) 85 | throws IOException { 86 | out(DEFAULT_WIDTH, DEFAULT_HEIGHT, len, font, request, response); 87 | } 88 | 89 | /** 90 | * 输出验证码 91 | * 92 | * @param width 宽度 93 | * @param height 高度 94 | * @param len 长度 95 | * @param font 字体 96 | * @param request HttpServletRequest 97 | * @param response HttpServletResponse 98 | * @throws IOException IO异常 99 | */ 100 | public static void out(int width, int height, int len, Font font, HttpServletRequest request, HttpServletResponse response) 101 | throws IOException { 102 | SpecCaptcha specCaptcha = new SpecCaptcha(width, height, len); 103 | if (font != null) { 104 | specCaptcha.setFont(font); 105 | } 106 | out(specCaptcha, request, response); 107 | } 108 | 109 | 110 | /** 111 | * 输出验证码 112 | * 113 | * @param captcha Captcha 114 | * @param request HttpServletRequest 115 | * @param response HttpServletResponse 116 | * @throws IOException IO异常 117 | */ 118 | public static void out(Captcha captcha, HttpServletRequest request, HttpServletResponse response) 119 | throws IOException { 120 | setHeader(response); 121 | request.getSession().setAttribute(SESSION_KEY, captcha.text().toLowerCase()); 122 | captcha.out(response.getOutputStream()); 123 | } 124 | 125 | /** 126 | * 验证验证码 127 | * 128 | * @param code 用户输入的验证码 129 | * @param request HttpServletRequest 130 | * @return 是否正确 131 | */ 132 | public static boolean ver(String code, HttpServletRequest request) { 133 | if (code != null) { 134 | String captcha = (String) request.getSession().getAttribute(SESSION_KEY); 135 | return code.trim().toLowerCase().equals(captcha); 136 | } 137 | return false; 138 | } 139 | 140 | /** 141 | * 清除session中的验证码 142 | * 143 | * @param request HttpServletRequest 144 | */ 145 | public static void clear(HttpServletRequest request) { 146 | request.getSession().removeAttribute(SESSION_KEY); 147 | } 148 | 149 | /** 150 | * 设置相应头 151 | * 152 | * @param response HttpServletResponse 153 | */ 154 | public static void setHeader(HttpServletResponse response) { 155 | response.setContentType("image/gif"); 156 | response.setHeader("Pragma", "No-cache"); 157 | response.setHeader("Cache-Control", "no-cache"); 158 | response.setDateHeader("Expires", 0); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/utils/Encoder.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha.utils; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | /** 7 | * 8 | */ 9 | public class Encoder { 10 | private static final int EOF = -1; 11 | // 图片的宽高 12 | private int imgW, imgH; 13 | private byte[] pixAry; 14 | private int initCodeSize; // 验证码位数 15 | private int remaining; // 剩余数量 16 | private int curPixel; // 像素 17 | 18 | static final int BITS = 12; 19 | 20 | static final int HSIZE = 5003; // 80% 占用率 21 | 22 | int n_bits; // number of bits/code 23 | int maxbits = BITS; // user settable max # bits/code 24 | int maxcode; // maximum code, given n_bits 25 | int maxmaxcode = 1 << BITS; // should NEVER generate this code 26 | 27 | int[] htab = new int[HSIZE]; 28 | int[] codetab = new int[HSIZE]; 29 | 30 | int hsize = HSIZE; // for dynamic table sizing 31 | 32 | int free_ent = 0; // first unused entry 33 | 34 | // block compression parameters -- after all codes are used up, 35 | // and compression rate changes, start over. 36 | boolean clear_flg = false; 37 | 38 | // Algorithm: use open addressing double hashing (no chaining) on the 39 | // prefix code / next character combination. We do a variant of Knuth's 40 | // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime 41 | // secondary probe. Here, the modular division first probe is gives way 42 | // to a faster exclusive-or manipulation. Also do block compression with 43 | // an adaptive reset, whereby the code table is cleared when the compression 44 | // ratio decreases, but after the table fills. The variable-length output 45 | // codes are re-sized at this point, and a special CLEAR code is generated 46 | // for the decompressor. Late addition: construct the table according to 47 | // file size for noticeable speed improvement on small files. Please direct 48 | // questions about this implementation to ames!jaw. 49 | 50 | int g_init_bits; 51 | 52 | int ClearCode; 53 | int EOFCode; 54 | 55 | // output 56 | // 57 | // Output the given code. 58 | // Inputs: 59 | // code: A n_bits-bit integer. If == -1, then EOF. This assumes 60 | // that n_bits =< wordsize - 1. 61 | // Outputs: 62 | // Outputs code to the file. 63 | // Assumptions: 64 | // Chars are 8 bits long. 65 | // Algorithm: 66 | // Maintain a BITS character long buffer (so that 8 codes will 67 | // fit in it exactly). Use the VAX insv instruction to insert each 68 | // code in turn. When the buffer fills up empty it and start over. 69 | 70 | int cur_accum = 0; 71 | int cur_bits = 0; 72 | 73 | int masks[] = 74 | { 75 | 0x0000, 76 | 0x0001, 77 | 0x0003, 78 | 0x0007, 79 | 0x000F, 80 | 0x001F, 81 | 0x003F, 82 | 0x007F, 83 | 0x00FF, 84 | 0x01FF, 85 | 0x03FF, 86 | 0x07FF, 87 | 0x0FFF, 88 | 0x1FFF, 89 | 0x3FFF, 90 | 0x7FFF, 91 | 0xFFFF}; 92 | 93 | // Number of characters so far in this 'packet' 94 | int a_count; 95 | 96 | // Define the storage for the packet accumulator 97 | byte[] accum = new byte[256]; 98 | 99 | //---------------------------------------------------------------------------- 100 | 101 | /** 102 | * @param width 宽度 103 | * @param height 高度 104 | * @param pixels 像素 105 | * @param color_depth 颜色 106 | */ 107 | Encoder(int width, int height, byte[] pixels, int color_depth) { 108 | imgW = width; 109 | imgH = height; 110 | pixAry = pixels; 111 | initCodeSize = Math.max(2, color_depth); 112 | } 113 | 114 | // Add a character to the end of the current packet, and if it is 254 115 | // characters, flush the packet to disk. 116 | 117 | /** 118 | * @param c 字节 119 | * @param outs 输出流 120 | * @throws IOException IO异常 121 | */ 122 | void char_out(byte c, OutputStream outs) throws IOException { 123 | accum[a_count++] = c; 124 | if (a_count >= 254) 125 | flush_char(outs); 126 | } 127 | 128 | // Clear out the hash table 129 | 130 | // table clear for block compress 131 | 132 | /** 133 | * @param outs 输出流 134 | * @throws IOException IO异常 135 | */ 136 | void cl_block(OutputStream outs) throws IOException { 137 | cl_hash(hsize); 138 | free_ent = ClearCode + 2; 139 | clear_flg = true; 140 | 141 | output(ClearCode, outs); 142 | } 143 | 144 | // reset code table 145 | 146 | /** 147 | * @param hsize int 148 | */ 149 | void cl_hash(int hsize) { 150 | for (int i = 0; i < hsize; ++i) 151 | htab[i] = -1; 152 | } 153 | 154 | /** 155 | * @param init_bits int 156 | * @param outs 输出流 157 | * @throws IOException IO异常 158 | */ 159 | void compress(int init_bits, OutputStream outs) throws IOException { 160 | int fcode; 161 | int i /* = 0 */; 162 | int c; 163 | int ent; 164 | int disp; 165 | int hsize_reg; 166 | int hshift; 167 | 168 | // Set up the globals: g_init_bits - initial number of bits 169 | g_init_bits = init_bits; 170 | 171 | // Set up the necessary values 172 | clear_flg = false; 173 | n_bits = g_init_bits; 174 | maxcode = MAXCODE(n_bits); 175 | 176 | ClearCode = 1 << (init_bits - 1); 177 | EOFCode = ClearCode + 1; 178 | free_ent = ClearCode + 2; 179 | 180 | a_count = 0; // clear packet 181 | 182 | ent = nextPixel(); 183 | 184 | hshift = 0; 185 | for (fcode = hsize; fcode < 65536; fcode *= 2) 186 | ++hshift; 187 | hshift = 8 - hshift; // set hash code range bound 188 | 189 | hsize_reg = hsize; 190 | cl_hash(hsize_reg); // clear hash table 191 | 192 | output(ClearCode, outs); 193 | 194 | outer_loop: 195 | while ((c = nextPixel()) != EOF) { 196 | fcode = (c << maxbits) + ent; 197 | i = (c << hshift) ^ ent; // xor hashing 198 | 199 | if (htab[i] == fcode) { 200 | ent = codetab[i]; 201 | continue; 202 | } else if (htab[i] >= 0) // non-empty slot 203 | { 204 | disp = hsize_reg - i; // secondary hash (after G. Knott) 205 | if (i == 0) 206 | disp = 1; 207 | do { 208 | if ((i -= disp) < 0) 209 | i += hsize_reg; 210 | 211 | if (htab[i] == fcode) { 212 | ent = codetab[i]; 213 | continue outer_loop; 214 | } 215 | } while (htab[i] >= 0); 216 | } 217 | output(ent, outs); 218 | ent = c; 219 | if (free_ent < maxmaxcode) { 220 | codetab[i] = free_ent++; // code -> hashtable 221 | htab[i] = fcode; 222 | } else 223 | cl_block(outs); 224 | } 225 | // Put out the final code. 226 | output(ent, outs); 227 | output(EOFCode, outs); 228 | } 229 | 230 | //---------------------------------------------------------------------------- 231 | 232 | /** 233 | * @param os 输出流 234 | * @throws IOException IO异常 235 | */ 236 | void encode(OutputStream os) throws IOException { 237 | os.write(initCodeSize); // write "initial code size" byte 238 | 239 | remaining = imgW * imgH; // reset navigation variables 240 | curPixel = 0; 241 | 242 | compress(initCodeSize + 1, os); // compress and write the pixel data 243 | 244 | os.write(0); // write block terminator 245 | } 246 | 247 | // Flush the packet to disk, and reset the accumulator 248 | 249 | /** 250 | * @param outs 输出流 251 | * @throws IOException IO异常 252 | */ 253 | void flush_char(OutputStream outs) throws IOException { 254 | if (a_count > 0) { 255 | outs.write(a_count); 256 | outs.write(accum, 0, a_count); 257 | a_count = 0; 258 | } 259 | } 260 | 261 | /** 262 | * @param n_bits int 263 | * @return int 264 | */ 265 | final int MAXCODE(int n_bits) { 266 | return (1 << n_bits) - 1; 267 | } 268 | 269 | //---------------------------------------------------------------------------- 270 | // Return the next pixel from the image 271 | //---------------------------------------------------------------------------- 272 | 273 | /** 274 | * @return int 275 | */ 276 | private int nextPixel() { 277 | if (remaining == 0) 278 | return EOF; 279 | 280 | --remaining; 281 | 282 | byte pix = pixAry[curPixel++]; 283 | 284 | return pix & 0xff; 285 | } 286 | 287 | /** 288 | * @param code int 289 | * @param outs 输出流 290 | * @throws IOException IO异常 291 | */ 292 | void output(int code, OutputStream outs) throws IOException { 293 | cur_accum &= masks[cur_bits]; 294 | 295 | if (cur_bits > 0) 296 | cur_accum |= (code << cur_bits); 297 | else 298 | cur_accum = code; 299 | 300 | cur_bits += n_bits; 301 | 302 | while (cur_bits >= 8) { 303 | char_out((byte) (cur_accum & 0xff), outs); 304 | cur_accum >>= 8; 305 | cur_bits -= 8; 306 | } 307 | 308 | // If the next entry is going to be too big for the code size, 309 | // then increase it, if possible. 310 | if (free_ent > maxcode || clear_flg) { 311 | if (clear_flg) { 312 | maxcode = MAXCODE(n_bits = g_init_bits); 313 | clear_flg = false; 314 | } else { 315 | ++n_bits; 316 | if (n_bits == maxbits) 317 | maxcode = maxmaxcode; 318 | else 319 | maxcode = MAXCODE(n_bits); 320 | } 321 | } 322 | 323 | if (code == EOFCode) { 324 | // At EOF, write the rest of the buffer. 325 | while (cur_bits > 0) { 326 | char_out((byte) (cur_accum & 0xff), outs); 327 | cur_accum >>= 8; 328 | cur_bits -= 8; 329 | } 330 | 331 | flush_char(outs); 332 | } 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/utils/GifEncoder.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha.utils; 2 | 3 | import java.awt.*; 4 | import java.awt.image.BufferedImage; 5 | import java.awt.image.DataBufferByte; 6 | import java.io.BufferedOutputStream; 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.OutputStream; 11 | 12 | /** 13 | * Gif生成工具 14 | * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or 15 | * more frames. 16 | *
 17 |  * Example:
 18 |  *    AnimatedGifEncoder e = new AnimatedGifEncoder();
 19 |  *    e.start(outputFileName);
 20 |  *    e.setDelay(1000);   // 1 frame per sec
 21 |  *    e.addFrame(image1);
 22 |  *    e.addFrame(image2);
 23 |  *    e.finish();
 24 |  * 
25 | * No copyright asserted on the source code of this class. May be used 26 | * for any purpose, however, refer to the Unisys LZW patent for restrictions 27 | * on use of the associated Encoder class. Please forward any corrections 28 | * to questions at fmsware.com. 29 | */ 30 | public class GifEncoder { 31 | protected int width; // image size 32 | protected int height; 33 | protected Color transparent = null; // transparent color if given 34 | protected int transIndex; // transparent index in color table 35 | protected int repeat = -1; // no repeat 36 | protected int delay = 0; // frame delay (hundredths) 37 | protected boolean started = false; // ready to output frames 38 | protected OutputStream out; 39 | protected BufferedImage image; // current frame 40 | protected byte[] pixels; // BGR byte array from frame 41 | protected byte[] indexedPixels; // converted frame indexed to palette 42 | protected int colorDepth; // number of bit planes 43 | protected byte[] colorTab; // RGB palette 44 | protected boolean[] usedEntry = new boolean[256]; // active palette entries 45 | protected int palSize = 7; // color table size (bits-1) 46 | protected int dispose = -1; // disposal code (-1 = use default) 47 | protected boolean closeStream = false; // close stream when finished 48 | protected boolean firstFrame = true; 49 | protected boolean sizeSet = false; // if false, get size from first frame 50 | protected int sample = 10; // default sample interval for quantizer 51 | 52 | /** 53 | * Sets the delay time between each frame, or changes it 54 | * for subsequent frames (applies to last frame added). 55 | * 56 | * @param ms int delay time in milliseconds 57 | */ 58 | public void setDelay(int ms) { 59 | delay = Math.round(ms / 10.0f); 60 | } 61 | 62 | /** 63 | * Sets the GIF frame disposal code for the last added frame 64 | * and any subsequent frames. Default is 0 if no transparent 65 | * color has been set, otherwise 2. 66 | * 67 | * @param code int disposal code. 68 | */ 69 | public void setDispose(int code) { 70 | if (code >= 0) { 71 | dispose = code; 72 | } 73 | } 74 | 75 | /** 76 | * Sets the number of times the set of GIF frames 77 | * should be played. Default is 1; 0 means play 78 | * indefinitely. Must be invoked before the first 79 | * image is added. 80 | * 81 | * @param iter int number of iterations. 82 | */ 83 | public void setRepeat(int iter) { 84 | if (iter >= 0) { 85 | repeat = iter; 86 | } 87 | } 88 | 89 | /** 90 | * Sets the transparent color for the last added frame 91 | * and any subsequent frames. 92 | * Since all colors are subject to modification 93 | * in the quantization process, the color in the final 94 | * palette for each frame closest to the given color 95 | * becomes the transparent color for that frame. 96 | * May be set to null to indicate no transparent color. 97 | * 98 | * @param c Color to be treated as transparent on display. 99 | */ 100 | public void setTransparent(Color c) { 101 | transparent = c; 102 | } 103 | 104 | /** 105 | * Adds next GIF frame. The frame is not written immediately, but is 106 | * actually deferred until the next frame is received so that timing 107 | * data can be inserted. Invoking finish() flushes all 108 | * frames. If setSize was not invoked, the size of the 109 | * first image is used for all subsequent frames. 110 | * 111 | * @param im BufferedImage containing frame to write. 112 | * @return true if successful. 113 | */ 114 | public boolean addFrame(BufferedImage im) { 115 | if ((im == null) || !started) { 116 | return false; 117 | } 118 | boolean ok = true; 119 | try { 120 | if (!sizeSet) { 121 | // use first frame's size 122 | setSize(im.getWidth(), im.getHeight()); 123 | } 124 | image = im; 125 | getImagePixels(); // convert to correct format if necessary 126 | analyzePixels(); // build color table & map pixels 127 | if (firstFrame) { 128 | writeLSD(); // logical screen descriptior 129 | writePalette(); // global color table 130 | if (repeat >= 0) { 131 | // use NS app extension to indicate reps 132 | writeNetscapeExt(); 133 | } 134 | } 135 | writeGraphicCtrlExt(); // write graphic control extension 136 | writeImageDesc(); // image descriptor 137 | if (!firstFrame) { 138 | writePalette(); // local color table 139 | } 140 | writePixels(); // encode and write pixel data 141 | firstFrame = false; 142 | } catch (IOException e) { 143 | ok = false; 144 | } 145 | 146 | return ok; 147 | } 148 | 149 | //added by alvaro 150 | public boolean outFlush() { 151 | boolean ok = true; 152 | try { 153 | out.flush(); 154 | return ok; 155 | } catch (IOException e) { 156 | ok = false; 157 | } 158 | 159 | return ok; 160 | } 161 | 162 | public byte[] getFrameByteArray() { 163 | return ((ByteArrayOutputStream) out).toByteArray(); 164 | } 165 | 166 | /** 167 | * Flushes any pending data and closes output file. 168 | * If writing to an OutputStream, the stream is not 169 | * closed. 170 | * 171 | * @return boolean 172 | */ 173 | public boolean finish() { 174 | if (!started) return false; 175 | boolean ok = true; 176 | started = false; 177 | try { 178 | out.write(0x3b); // gif trailer 179 | out.flush(); 180 | if (closeStream) { 181 | out.close(); 182 | } 183 | } catch (IOException e) { 184 | ok = false; 185 | } 186 | 187 | return ok; 188 | } 189 | 190 | public void reset() { 191 | // reset for subsequent use 192 | transIndex = 0; 193 | out = null; 194 | image = null; 195 | pixels = null; 196 | indexedPixels = null; 197 | colorTab = null; 198 | closeStream = false; 199 | firstFrame = true; 200 | } 201 | 202 | /** 203 | * Sets frame rate in frames per second. Equivalent to 204 | * setDelay(1000/fps). 205 | * 206 | * @param fps float frame rate (frames per second) 207 | */ 208 | public void setFrameRate(float fps) { 209 | if (fps != 0f) { 210 | delay = Math.round(100f / fps); 211 | } 212 | } 213 | 214 | /** 215 | * Sets quality of color quantization (conversion of images 216 | * to the maximum 256 colors allowed by the GIF specification). 217 | * Lower values (minimum = 1) produce better colors, but slow 218 | * processing significantly. 10 is the default, and produces 219 | * good color mapping at reasonable speeds. Values greater 220 | * than 20 do not yield significant improvements in speed. 221 | * 222 | * @param quality int greater than 0. 223 | */ 224 | public void setQuality(int quality) { 225 | if (quality < 1) quality = 1; 226 | sample = quality; 227 | } 228 | 229 | /** 230 | * Sets the GIF frame size. The default size is the 231 | * size of the first frame added if this method is 232 | * not invoked. 233 | * 234 | * @param w int frame width. 235 | * @param h int frame width. 236 | */ 237 | public void setSize(int w, int h) { 238 | if (started && !firstFrame) return; 239 | width = w; 240 | height = h; 241 | if (width < 1) width = 320; 242 | if (height < 1) height = 240; 243 | sizeSet = true; 244 | } 245 | 246 | /** 247 | * Initiates GIF file creation on the given stream. The stream 248 | * is not closed automatically. 249 | * 250 | * @param os OutputStream on which GIF images are written. 251 | * @return false if initial write failed. 252 | */ 253 | public boolean start(OutputStream os) { 254 | if (os == null) return false; 255 | boolean ok = true; 256 | closeStream = false; 257 | out = os; 258 | try { 259 | writeString("GIF89a"); // header 260 | } catch (IOException e) { 261 | ok = false; 262 | } 263 | return started = ok; 264 | } 265 | 266 | /** 267 | * Initiates writing of a GIF file with the specified name. 268 | * 269 | * @param file String containing output file name. 270 | * @return false if open or initial write failed. 271 | */ 272 | public boolean start(String file) { 273 | boolean ok = true; 274 | try { 275 | out = new BufferedOutputStream(new FileOutputStream(file)); 276 | ok = start(out); 277 | closeStream = true; 278 | } catch (IOException e) { 279 | ok = false; 280 | } 281 | return started = ok; 282 | } 283 | 284 | /** 285 | * Analyzes image colors and creates color map. 286 | */ 287 | protected void analyzePixels() { 288 | int len = pixels.length; 289 | int nPix = len / 3; 290 | indexedPixels = new byte[nPix]; 291 | Quant nq = new Quant(pixels, len, sample); 292 | // initialize quantizer 293 | colorTab = nq.process(); // create reduced palette 294 | // convert map from BGR to RGB 295 | for (int i = 0; i < colorTab.length; i += 3) { 296 | byte temp = colorTab[i]; 297 | colorTab[i] = colorTab[i + 2]; 298 | colorTab[i + 2] = temp; 299 | usedEntry[i / 3] = false; 300 | } 301 | // map image pixels to new palette 302 | int k = 0; 303 | for (int i = 0; i < nPix; i++) { 304 | int index = 305 | nq.map(pixels[k++] & 0xff, 306 | pixels[k++] & 0xff, 307 | pixels[k++] & 0xff); 308 | usedEntry[index] = true; 309 | indexedPixels[i] = (byte) index; 310 | } 311 | pixels = null; 312 | colorDepth = 8; 313 | palSize = 7; 314 | // get closest match to transparent color if specified 315 | if (transparent != null) { 316 | transIndex = findClosest(transparent); 317 | } 318 | } 319 | 320 | /** 321 | * Returns index of palette color closest to c 322 | * 323 | * @param c color 324 | * @return int 325 | */ 326 | protected int findClosest(Color c) { 327 | if (colorTab == null) return -1; 328 | int r = c.getRed(); 329 | int g = c.getGreen(); 330 | int b = c.getBlue(); 331 | int minpos = 0; 332 | int dmin = 256 * 256 * 256; 333 | int len = colorTab.length; 334 | for (int i = 0; i < len; ) { 335 | int dr = r - (colorTab[i++] & 0xff); 336 | int dg = g - (colorTab[i++] & 0xff); 337 | int db = b - (colorTab[i] & 0xff); 338 | int d = dr * dr + dg * dg + db * db; 339 | int index = i / 3; 340 | if (usedEntry[index] && (d < dmin)) { 341 | dmin = d; 342 | minpos = index; 343 | } 344 | i++; 345 | } 346 | return minpos; 347 | } 348 | 349 | /** 350 | * Extracts image pixels into byte array "pixels" 351 | */ 352 | protected void getImagePixels() { 353 | int w = image.getWidth(); 354 | int h = image.getHeight(); 355 | int type = image.getType(); 356 | if ((w != width) 357 | || (h != height) 358 | || (type != BufferedImage.TYPE_3BYTE_BGR)) { 359 | // create new image with right size/format 360 | BufferedImage temp = 361 | new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); 362 | Graphics2D g = temp.createGraphics(); 363 | g.drawImage(image, 0, 0, null); 364 | image = temp; 365 | } 366 | pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); 367 | } 368 | 369 | /** 370 | * Writes Graphic Control Extension 371 | * 372 | * @throws IOException IO异常 373 | */ 374 | protected void writeGraphicCtrlExt() throws IOException { 375 | out.write(0x21); // extension introducer 376 | out.write(0xf9); // GCE label 377 | out.write(4); // data block size 378 | int transp, disp; 379 | if (transparent == null) { 380 | transp = 0; 381 | disp = 0; // dispose = no action 382 | } else { 383 | transp = 1; 384 | disp = 2; // force clear if using transparent color 385 | } 386 | if (dispose >= 0) { 387 | disp = dispose & 7; // user override 388 | } 389 | disp <<= 2; 390 | 391 | // packed fields 392 | out.write(0 | // 1:3 reserved 393 | disp | // 4:6 disposal 394 | 0 | // 7 user input - 0 = none 395 | transp); // 8 transparency flag 396 | 397 | writeShort(delay); // delay x 1/100 sec 398 | out.write(transIndex); // transparent color index 399 | out.write(0); // block terminator 400 | } 401 | 402 | /** 403 | * Writes Image Descriptor 404 | * 405 | * @throws IOException IO异常 406 | */ 407 | protected void writeImageDesc() throws IOException { 408 | out.write(0x2c); // image separator 409 | writeShort(0); // image position x,y = 0,0 410 | writeShort(0); 411 | writeShort(width); // image size 412 | writeShort(height); 413 | // packed fields 414 | if (firstFrame) { 415 | // no LCT - GCT is used for first (or only) frame 416 | out.write(0); 417 | } else { 418 | // specify normal LCT 419 | out.write(0x80 | // 1 local color table 1=yes 420 | 0 | // 2 interlace - 0=no 421 | 0 | // 3 sorted - 0=no 422 | 0 | // 4-5 reserved 423 | palSize); // 6-8 size of color table 424 | } 425 | } 426 | 427 | /** 428 | * Writes Logical Screen Descriptor 429 | * 430 | * @throws IOException IO异常 431 | */ 432 | protected void writeLSD() throws IOException { 433 | // logical screen size 434 | writeShort(width); 435 | writeShort(height); 436 | // packed fields 437 | out.write((0x80 | // 1 : global color table flag = 1 (gct used) 438 | 0x70 | // 2-4 : color resolution = 7 439 | 0x00 | // 5 : gct sort flag = 0 440 | palSize)); // 6-8 : gct size 441 | 442 | out.write(0); // background color index 443 | out.write(0); // pixel aspect ratio - assume 1:1 444 | } 445 | 446 | /** 447 | * Writes Netscape application extension to define 448 | * repeat count. 449 | * 450 | * @throws IOException IO异常 451 | */ 452 | protected void writeNetscapeExt() throws IOException { 453 | out.write(0x21); // extension introducer 454 | out.write(0xff); // app extension label 455 | out.write(11); // block size 456 | writeString("NETSCAPE" + "2.0"); // app id + auth code 457 | out.write(3); // sub-block size 458 | out.write(1); // loop sub-block id 459 | writeShort(repeat); // loop count (extra iterations, 0=repeat forever) 460 | out.write(0); // block terminator 461 | } 462 | 463 | /** 464 | * Writes color table 465 | * 466 | * @throws IOException IO异常 467 | */ 468 | protected void writePalette() throws IOException { 469 | out.write(colorTab, 0, colorTab.length); 470 | int n = (3 * 256) - colorTab.length; 471 | for (int i = 0; i < n; i++) { 472 | out.write(0); 473 | } 474 | } 475 | 476 | /** 477 | * Encodes and writes pixel data 478 | * 479 | * @throws IOException IO异常 480 | */ 481 | protected void writePixels() throws IOException { 482 | Encoder encoder = new Encoder(width, height, indexedPixels, colorDepth); 483 | encoder.encode(out); 484 | } 485 | 486 | /** 487 | * Write 16-bit value to output stream, LSB first 488 | * 489 | * @param value int 490 | * @throws IOException IO异常 491 | */ 492 | protected void writeShort(int value) throws IOException { 493 | out.write(value & 0xff); 494 | out.write((value >> 8) & 0xff); 495 | } 496 | 497 | /** 498 | * Writes string to output stream 499 | * 500 | * @param s string 501 | * @throws IOException IO异常 502 | */ 503 | protected void writeString(String s) throws IOException { 504 | for (int i = 0; i < s.length(); i++) { 505 | out.write((byte) s.charAt(i)); 506 | } 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /src/main/java/com/wf/captcha/utils/Quant.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha.utils; 2 | 3 | /** 4 | * 5 | */ 6 | public class Quant { 7 | protected static final int netsize = 256; /* number of colours used */ 8 | 9 | /* four primes near 500 - assume no image has a length so large */ 10 | /* that it is divisible by all four primes */ 11 | protected static final int prime1 = 499; 12 | protected static final int prime2 = 491; 13 | protected static final int prime3 = 487; 14 | protected static final int prime4 = 503; 15 | 16 | protected static final int minpicturebytes = (3 * prime4); 17 | /* minimum size for input image */ 18 | 19 | /* Program Skeleton 20 | ---------------- 21 | [select samplefac in range 1..30] 22 | [read image from input file] 23 | pic = (unsigned char*) malloc(3*width*height); 24 | initnet(pic,3*width*height,samplefac); 25 | learn(); 26 | unbiasnet(); 27 | [write output image header, using writecolourmap(f)] 28 | inxbuild(); 29 | write output image using inxsearch(b,g,r) */ 30 | 31 | /* Network Definitions 32 | ------------------- */ 33 | 34 | protected static final int maxnetpos = (netsize - 1); 35 | protected static final int netbiasshift = 4; /* bias for colour values */ 36 | protected static final int ncycles = 100; /* no. of learning cycles */ 37 | 38 | /* defs for freq and bias */ 39 | protected static final int intbiasshift = 16; /* bias for fractions */ 40 | protected static final int intbias = (((int) 1) << intbiasshift); 41 | protected static final int gammashift = 10; /* gamma = 1024 */ 42 | protected static final int gamma = (((int) 1) << gammashift); 43 | protected static final int betashift = 10; 44 | protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */ 45 | protected static final int betagamma = 46 | (intbias << (gammashift - betashift)); 47 | 48 | /* defs for decreasing radius factor */ 49 | protected static final int initrad = (netsize >> 3); /* for 256 cols, radius starts */ 50 | protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */ 51 | protected static final int radiusbias = (((int) 1) << radiusbiasshift); 52 | protected static final int initradius = (initrad * radiusbias); /* and decreases by a */ 53 | protected static final int radiusdec = 30; /* factor of 1/30 each cycle */ 54 | 55 | /* defs for decreasing alpha factor */ 56 | protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */ 57 | protected static final int initalpha = (((int) 1) << alphabiasshift); 58 | 59 | protected int alphadec; /* biased by 10 bits */ 60 | 61 | /* radbias and alpharadbias used for radpower calculation */ 62 | protected static final int radbiasshift = 8; 63 | protected static final int radbias = (((int) 1) << radbiasshift); 64 | protected static final int alpharadbshift = (alphabiasshift + radbiasshift); 65 | protected static final int alpharadbias = (((int) 1) << alpharadbshift); 66 | 67 | /* Types and Global Variables 68 | -------------------------- */ 69 | 70 | protected byte[] thepicture; /* the input image itself */ 71 | protected int lengthcount; /* lengthcount = H*W*3 */ 72 | 73 | protected int samplefac; /* sampling factor 1..30 */ 74 | 75 | // typedef int pixel[4]; /* BGRc */ 76 | protected int[][] network; /* the network itself - [netsize][4] */ 77 | 78 | protected int[] netindex = new int[256]; 79 | /* for network lookup - really 256 */ 80 | 81 | protected int[] bias = new int[netsize]; 82 | /* bias and freq arrays for learning */ 83 | protected int[] freq = new int[netsize]; 84 | protected int[] radpower = new int[initrad]; 85 | /* radpower for precomputation */ 86 | 87 | /* Initialise network in range (0,0,0) to (255,255,255) and set parameters 88 | ----------------------------------------------------------------------- */ 89 | public Quant(byte[] thepic, int len, int sample) { 90 | 91 | int i; 92 | int[] p; 93 | 94 | thepicture = thepic; 95 | lengthcount = len; 96 | samplefac = sample; 97 | 98 | network = new int[netsize][]; 99 | for (i = 0; i < netsize; i++) { 100 | network[i] = new int[4]; 101 | p = network[i]; 102 | p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize; 103 | freq[i] = intbias / netsize; /* 1/netsize */ 104 | bias[i] = 0; 105 | } 106 | } 107 | 108 | public byte[] colorMap() { 109 | byte[] map = new byte[3 * netsize]; 110 | int[] index = new int[netsize]; 111 | for (int i = 0; i < netsize; i++) 112 | index[network[i][3]] = i; 113 | int k = 0; 114 | for (int i = 0; i < netsize; i++) { 115 | int j = index[i]; 116 | map[k++] = (byte) (network[j][0]); 117 | map[k++] = (byte) (network[j][1]); 118 | map[k++] = (byte) (network[j][2]); 119 | } 120 | return map; 121 | } 122 | 123 | /* Insertion sort of network and building of netindex[0..255] (to do after unbias) 124 | ------------------------------------------------------------------------------- */ 125 | public void inxbuild() { 126 | 127 | int i, j, smallpos, smallval; 128 | int[] p; 129 | int[] q; 130 | int previouscol, startpos; 131 | 132 | previouscol = 0; 133 | startpos = 0; 134 | for (i = 0; i < netsize; i++) { 135 | p = network[i]; 136 | smallpos = i; 137 | smallval = p[1]; /* index on g */ 138 | /* find smallest in i..netsize-1 */ 139 | for (j = i + 1; j < netsize; j++) { 140 | q = network[j]; 141 | if (q[1] < smallval) { /* index on g */ 142 | smallpos = j; 143 | smallval = q[1]; /* index on g */ 144 | } 145 | } 146 | q = network[smallpos]; 147 | /* swap p (i) and q (smallpos) entries */ 148 | if (i != smallpos) { 149 | j = q[0]; 150 | q[0] = p[0]; 151 | p[0] = j; 152 | j = q[1]; 153 | q[1] = p[1]; 154 | p[1] = j; 155 | j = q[2]; 156 | q[2] = p[2]; 157 | p[2] = j; 158 | j = q[3]; 159 | q[3] = p[3]; 160 | p[3] = j; 161 | } 162 | /* smallval entry is now in position i */ 163 | if (smallval != previouscol) { 164 | netindex[previouscol] = (startpos + i) >> 1; 165 | for (j = previouscol + 1; j < smallval; j++) 166 | netindex[j] = i; 167 | previouscol = smallval; 168 | startpos = i; 169 | } 170 | } 171 | netindex[previouscol] = (startpos + maxnetpos) >> 1; 172 | for (j = previouscol + 1; j < 256; j++) 173 | netindex[j] = maxnetpos; /* really 256 */ 174 | } 175 | 176 | /* Main Learning Loop 177 | ------------------ */ 178 | public void learn() { 179 | 180 | int i, j, b, g, r; 181 | int radius, rad, alpha, step, delta, samplepixels; 182 | byte[] p; 183 | int pix, lim; 184 | 185 | if (lengthcount < minpicturebytes) 186 | samplefac = 1; 187 | alphadec = 30 + ((samplefac - 1) / 3); 188 | p = thepicture; 189 | pix = 0; 190 | lim = lengthcount; 191 | samplepixels = lengthcount / (3 * samplefac); 192 | delta = samplepixels / ncycles; 193 | alpha = initalpha; 194 | radius = initradius; 195 | 196 | rad = radius >> radiusbiasshift; 197 | if (rad <= 1) 198 | rad = 0; 199 | for (i = 0; i < rad; i++) 200 | radpower[i] = 201 | alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); 202 | 203 | //fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad); 204 | 205 | if (lengthcount < minpicturebytes) 206 | step = 3; 207 | else if ((lengthcount % prime1) != 0) 208 | step = 3 * prime1; 209 | else { 210 | if ((lengthcount % prime2) != 0) 211 | step = 3 * prime2; 212 | else { 213 | if ((lengthcount % prime3) != 0) 214 | step = 3 * prime3; 215 | else 216 | step = 3 * prime4; 217 | } 218 | } 219 | 220 | i = 0; 221 | while (i < samplepixels) { 222 | b = (p[pix + 0] & 0xff) << netbiasshift; 223 | g = (p[pix + 1] & 0xff) << netbiasshift; 224 | r = (p[pix + 2] & 0xff) << netbiasshift; 225 | j = contest(b, g, r); 226 | 227 | altersingle(alpha, j, b, g, r); 228 | if (rad != 0) 229 | alterneigh(rad, j, b, g, r); /* alter neighbours */ 230 | 231 | pix += step; 232 | if (pix >= lim) 233 | pix -= lengthcount; 234 | 235 | i++; 236 | if (delta == 0) 237 | delta = 1; 238 | if (i % delta == 0) { 239 | alpha -= alpha / alphadec; 240 | radius -= radius / radiusdec; 241 | rad = radius >> radiusbiasshift; 242 | if (rad <= 1) 243 | rad = 0; 244 | for (j = 0; j < rad; j++) 245 | radpower[j] = 246 | alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); 247 | } 248 | } 249 | //fprintf(stderr,"finished 1D learning: final alpha=%f !\n",((float)alpha)/initalpha); 250 | } 251 | 252 | /* Search for BGR values 0..255 (after net is unbiased) and return colour index 253 | ---------------------------------------------------------------------------- */ 254 | public int map(int b, int g, int r) { 255 | 256 | int i, j, dist, a, bestd; 257 | int[] p; 258 | int best; 259 | 260 | bestd = 1000; /* biggest possible dist is 256*3 */ 261 | best = -1; 262 | i = netindex[g]; /* index on g */ 263 | j = i - 1; /* start at netindex[g] and work outwards */ 264 | 265 | while ((i < netsize) || (j >= 0)) { 266 | if (i < netsize) { 267 | p = network[i]; 268 | dist = p[1] - g; /* inx key */ 269 | if (dist >= bestd) 270 | i = netsize; /* stop iter */ 271 | else { 272 | i++; 273 | if (dist < 0) 274 | dist = -dist; 275 | a = p[0] - b; 276 | if (a < 0) 277 | a = -a; 278 | dist += a; 279 | if (dist < bestd) { 280 | a = p[2] - r; 281 | if (a < 0) 282 | a = -a; 283 | dist += a; 284 | if (dist < bestd) { 285 | bestd = dist; 286 | best = p[3]; 287 | } 288 | } 289 | } 290 | } 291 | if (j >= 0) { 292 | p = network[j]; 293 | dist = g - p[1]; /* inx key - reverse dif */ 294 | if (dist >= bestd) 295 | j = -1; /* stop iter */ 296 | else { 297 | j--; 298 | if (dist < 0) 299 | dist = -dist; 300 | a = p[0] - b; 301 | if (a < 0) 302 | a = -a; 303 | dist += a; 304 | if (dist < bestd) { 305 | a = p[2] - r; 306 | if (a < 0) 307 | a = -a; 308 | dist += a; 309 | if (dist < bestd) { 310 | bestd = dist; 311 | best = p[3]; 312 | } 313 | } 314 | } 315 | } 316 | } 317 | return (best); 318 | } 319 | 320 | public byte[] process() { 321 | learn(); 322 | unbiasnet(); 323 | inxbuild(); 324 | return colorMap(); 325 | } 326 | 327 | /* Unbias network to give byte values 0..255 and record position i to prepare for sort 328 | ----------------------------------------------------------------------------------- */ 329 | public void unbiasnet() { 330 | 331 | int i, j; 332 | 333 | for (i = 0; i < netsize; i++) { 334 | network[i][0] >>= netbiasshift; 335 | network[i][1] >>= netbiasshift; 336 | network[i][2] >>= netbiasshift; 337 | network[i][3] = i; /* record colour no */ 338 | } 339 | } 340 | 341 | /* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] 342 | --------------------------------------------------------------------------------- */ 343 | protected void alterneigh(int rad, int i, int b, int g, int r) { 344 | 345 | int j, k, lo, hi, a, m; 346 | int[] p; 347 | 348 | lo = i - rad; 349 | if (lo < -1) 350 | lo = -1; 351 | hi = i + rad; 352 | if (hi > netsize) 353 | hi = netsize; 354 | 355 | j = i + 1; 356 | k = i - 1; 357 | m = 1; 358 | while ((j < hi) || (k > lo)) { 359 | a = radpower[m++]; 360 | if (j < hi) { 361 | p = network[j++]; 362 | try { 363 | p[0] -= (a * (p[0] - b)) / alpharadbias; 364 | p[1] -= (a * (p[1] - g)) / alpharadbias; 365 | p[2] -= (a * (p[2] - r)) / alpharadbias; 366 | } catch (Exception e) { 367 | } // prevents 1.3 miscompilation 368 | } 369 | if (k > lo) { 370 | p = network[k--]; 371 | try { 372 | p[0] -= (a * (p[0] - b)) / alpharadbias; 373 | p[1] -= (a * (p[1] - g)) / alpharadbias; 374 | p[2] -= (a * (p[2] - r)) / alpharadbias; 375 | } catch (Exception e) { 376 | } 377 | } 378 | } 379 | } 380 | 381 | /* Move neuron i towards biased (b,g,r) by factor alpha 382 | ---------------------------------------------------- */ 383 | protected void altersingle(int alpha, int i, int b, int g, int r) { 384 | 385 | /* alter hit neuron */ 386 | int[] n = network[i]; 387 | n[0] -= (alpha * (n[0] - b)) / initalpha; 388 | n[1] -= (alpha * (n[1] - g)) / initalpha; 389 | n[2] -= (alpha * (n[2] - r)) / initalpha; 390 | } 391 | 392 | /* Search for biased BGR values 393 | ---------------------------- */ 394 | protected int contest(int b, int g, int r) { 395 | 396 | /* finds closest neuron (min dist) and updates freq */ 397 | /* finds best neuron (min dist-bias) and returns position */ 398 | /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ 399 | /* bias[i] = gamma*((1/netsize)-freq[i]) */ 400 | 401 | int i, dist, a, biasdist, betafreq; 402 | int bestpos, bestbiaspos, bestd, bestbiasd; 403 | int[] n; 404 | 405 | bestd = ~(((int) 1) << 31); 406 | bestbiasd = bestd; 407 | bestpos = -1; 408 | bestbiaspos = bestpos; 409 | 410 | for (i = 0; i < netsize; i++) { 411 | n = network[i]; 412 | dist = n[0] - b; 413 | if (dist < 0) 414 | dist = -dist; 415 | a = n[1] - g; 416 | if (a < 0) 417 | a = -a; 418 | dist += a; 419 | a = n[2] - r; 420 | if (a < 0) 421 | a = -a; 422 | dist += a; 423 | if (dist < bestd) { 424 | bestd = dist; 425 | bestpos = i; 426 | } 427 | biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); 428 | if (biasdist < bestbiasd) { 429 | bestbiasd = biasdist; 430 | bestbiaspos = i; 431 | } 432 | betafreq = (freq[i] >> betashift); 433 | freq[i] -= betafreq; 434 | bias[i] += (betafreq << gammashift); 435 | } 436 | freq[bestpos] += beta; 437 | bias[bestpos] -= betagamma; 438 | return (bestbiaspos); 439 | } 440 | } -------------------------------------------------------------------------------- /src/main/resources/actionj.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ele-admin/EasyCaptcha/f6cb6062f55704661adffe8ad571bfff7412fc52/src/main/resources/actionj.ttf -------------------------------------------------------------------------------- /src/main/resources/epilog.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ele-admin/EasyCaptcha/f6cb6062f55704661adffe8ad571bfff7412fc52/src/main/resources/epilog.ttf -------------------------------------------------------------------------------- /src/main/resources/fresnel.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ele-admin/EasyCaptcha/f6cb6062f55704661adffe8ad571bfff7412fc52/src/main/resources/fresnel.ttf -------------------------------------------------------------------------------- /src/main/resources/headache.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ele-admin/EasyCaptcha/f6cb6062f55704661adffe8ad571bfff7412fc52/src/main/resources/headache.ttf -------------------------------------------------------------------------------- /src/main/resources/lexo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ele-admin/EasyCaptcha/f6cb6062f55704661adffe8ad571bfff7412fc52/src/main/resources/lexo.ttf -------------------------------------------------------------------------------- /src/main/resources/prefix.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ele-admin/EasyCaptcha/f6cb6062f55704661adffe8ad571bfff7412fc52/src/main/resources/prefix.ttf -------------------------------------------------------------------------------- /src/main/resources/progbot.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ele-admin/EasyCaptcha/f6cb6062f55704661adffe8ad571bfff7412fc52/src/main/resources/progbot.ttf -------------------------------------------------------------------------------- /src/main/resources/ransom.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ele-admin/EasyCaptcha/f6cb6062f55704661adffe8ad571bfff7412fc52/src/main/resources/ransom.ttf -------------------------------------------------------------------------------- /src/main/resources/robot.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ele-admin/EasyCaptcha/f6cb6062f55704661adffe8ad571bfff7412fc52/src/main/resources/robot.ttf -------------------------------------------------------------------------------- /src/main/resources/scandal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ele-admin/EasyCaptcha/f6cb6062f55704661adffe8ad571bfff7412fc52/src/main/resources/scandal.ttf -------------------------------------------------------------------------------- /src/test/java/com/wf/captcha/CaptchaTest.java: -------------------------------------------------------------------------------- 1 | package com.wf.captcha; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | 8 | /** 9 | * 测试类 10 | * Created by 王帆 on 2018-07-27 上午 10:08. 11 | */ 12 | public class CaptchaTest { 13 | 14 | @Test 15 | public void test() throws Exception { 16 | /*for (int i = 0; i < 10; i++) { 17 | SpecCaptcha specCaptcha = new SpecCaptcha(); 18 | specCaptcha.setLen(4); 19 | specCaptcha.setFont(i, 32f); 20 | System.out.println(specCaptcha.text()); 21 | specCaptcha.out(new FileOutputStream(new File("C:/Java/aa" + i + ".png"))); 22 | }*/ 23 | } 24 | 25 | @Test 26 | public void testGIf() throws Exception { 27 | /*for (int i = 0; i < 10; i++) { 28 | GifCaptcha gifCaptcha = new GifCaptcha(); 29 | gifCaptcha.setLen(5); 30 | gifCaptcha.setFont(i, 32f); 31 | System.out.println(gifCaptcha.text()); 32 | gifCaptcha.out(new FileOutputStream(new File("C:/Java/aa" + i + ".gif"))); 33 | }*/ 34 | } 35 | 36 | @Test 37 | public void testHan() throws Exception { 38 | /*for (int i = 0; i < 10; i++) { 39 | ChineseCaptcha chineseCaptcha = new ChineseCaptcha(); 40 | System.out.println(chineseCaptcha.text()); 41 | chineseCaptcha.out(new FileOutputStream(new File("C:/Java/aa" + i + ".png"))); 42 | }*/ 43 | } 44 | 45 | @Test 46 | public void testGifHan() throws Exception { 47 | /*for (int i = 0; i < 10; i++) { 48 | ChineseGifCaptcha chineseGifCaptcha = new ChineseGifCaptcha(); 49 | System.out.println(chineseGifCaptcha.text()); 50 | chineseGifCaptcha.out(new FileOutputStream(new File("C:/Java/aa" + i + ".gif"))); 51 | }*/ 52 | } 53 | 54 | @Test 55 | public void testArit() throws Exception { 56 | /*for (int i = 0; i < 10; i++) { 57 | ArithmeticCaptcha specCaptcha = new ArithmeticCaptcha(); 58 | specCaptcha.setLen(3); 59 | specCaptcha.setFont(i, 28f); 60 | System.out.println(specCaptcha.getArithmeticString() + " " + specCaptcha.text()); 61 | specCaptcha.out(new FileOutputStream(new File("C:/Java/aa" + i + ".png"))); 62 | }*/ 63 | } 64 | 65 | @Test 66 | public void testBase64() throws Exception { 67 | /*GifCaptcha specCaptcha = new GifCaptcha(); 68 | System.out.println(specCaptcha.toBase64(""));*/ 69 | } 70 | 71 | } 72 | --------------------------------------------------------------------------------