Git Product home page Git Product logo

easycaptcha's Introduction

EasyCaptcha

MavenCentral Hex.pm

1.简介

 Java图形验证码,支持gif、中文、算术等类型,可用于Java Web、JavaSE等项目。


2.效果展示

验证码    验证码    验证码
验证码    验证码    验证码

算术类型:

验证码    验证码    验证码

中文类型:

验证码    验证码    验证码

内置字体:

验证码    验证码    验证码


3.导入项目

3.1.gradle方式的引入

dependencies {
    compile 'com.github.whvcse:easy-captcha:1.6.2'
}

3.2.maven方式引入

<dependencies>
   <dependency>
      <groupId>com.github.whvcse</groupId>
      <artifactId>easy-captcha</artifactId>
      <version>1.6.2</version>
   </dependency>
</dependencies>

3.3.jar包下载

easy-captcha-1.6.2.jar

maven导入jar包,在项目根目录创建libs文件夹,然后pom.xml添加如下:

<dependency>
  <groupId>com.github.whvcse</groupId>
  <artifactId>easy-captcha</artifactId>
  <version>1.6.1</version>
  <systemPath>${basedir}/libs/easy-captcha-1.6.2.jar</systemPath>
</dependency>

4.使用方法

4.1.在SpringMVC中使用

@Controller
public class CaptchaController {
    
    @RequestMapping("/captcha")
    public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        CaptchaUtil.out(request, response);
    }
}

前端html代码:

<img src="/captcha" width="130px" height="48px" />

不要忘了把/captcha路径排除登录拦截,比如shiro的拦截。

4.2.在servlet中使用

web.xml中配置servlet:

<web-app>
    <!-- 图形验证码servlet -->
    <servlet>
        <servlet-name>CaptchaServlet</servlet-name>
        <servlet-class>com.wf.captcha.servlet.CaptchaServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>CaptchaServlet</servlet-name>
        <url-pattern>/captcha</url-pattern>
    </servlet-mapping>
</web-app>

前端html代码:

<img src="/captcha" width="130px" height="48px" />

4.3.判断验证码是否正确

@Controller
public class LoginController {
    
    @PostMapping("/login")
    public JsonResult login(String username,String password,String verCode){
        if (!CaptchaUtil.ver(verCode, request)) {
            CaptchaUtil.clear(request);  // 清除session中的验证码
            return JsonResult.error("验证码不正确");
        }
    }   
}

4.4.设置宽高和位数

@Controller
public class CaptchaController {
    
    @RequestMapping("/captcha")
    public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 设置位数
        CaptchaUtil.out(5, request, response);
        // 设置宽、高、位数
        CaptchaUtil.out(130, 48, 5, request, response);
        
        // 使用gif验证码
        GifCaptcha gifCaptcha = new GifCaptcha(130,48,4);
        CaptchaUtil.out(gifCaptcha, request, response);
    }
}

4.5.不使用工具类

 CaptchaUtil封装了输出验证码、存session、判断验证码等功能,也可以不使用此工具类:

@Controller
public class CaptchaController {
    
    @RequestMapping("/captcha")
    public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        
        // 三个参数分别为宽、高、位数
        SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
        // 设置字体
        specCaptcha.setFont(new Font("Verdana", Font.PLAIN, 32));  // 有默认字体,可以不用设置
        // 设置类型,纯数字、纯字母、字母数字混合
        specCaptcha.setCharType(Captcha.TYPE_ONLY_NUMBER);
        
        // 验证码存入session
        request.getSession().setAttribute("captcha", specCaptcha.text().toLowerCase());
        
        // 输出图片流
        specCaptcha.out(response.getOutputStream());
    }
    
    @PostMapping("/login")
    public JsonResult login(String username,String password,String verCode){
        // 获取session中的验证码
        String sessionCode = request.getSession().getAttribute("captcha");
        // 判断验证码
        if (verCode==null || !sessionCode.equals(verCode.trim().toLowerCase())) {
            return JsonResult.error("验证码不正确");
        }
    }  
}

5.更多设置

5.1.验证码类型

public class Test {
    
    public static void main(String[] args) {
        // png类型
        SpecCaptcha captcha = new SpecCaptcha(130, 48);
        captcha.text();  // 获取验证码的字符
        captcha.textChar();  // 获取验证码的字符数组
        
        // gif类型
        GifCaptcha captcha = new GifCaptcha(130, 48);
        
        // 中文类型
        ChineseCaptcha captcha = new ChineseCaptcha(130, 48);
        
        // 中文gif类型
        ChineseGifCaptcha captcha = new ChineseGifCaptcha(130, 48);
        
        // 算术类型
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 48);
        captcha.setLen(3);  // 几位数运算,默认是两位
        captcha.getArithmeticString();  // 获取运算的公式:3+2=?
        captcha.text();  // 获取运算的结果:5
        
        captcha.out(outputStream);  // 输出验证码
    }
}

注意:
 算术验证码的len表示是几位数运算,而其他验证码的len表示验证码的位数,算术验证码的text()表示的是公式的结果, 对于算术验证码,你应该把公式的结果存储session,而不是公式。

5.2.验证码字符类型

类型 描述
TYPE_DEFAULT 数字和字母混合
TYPE_ONLY_NUMBER 纯数字
TYPE_ONLY_CHAR 纯字母
TYPE_ONLY_UPPER 纯大写字母
TYPE_ONLY_LOWER 纯小写字母
TYPE_NUM_AND_UPPER 数字和大写字母

使用方法:

SpecCaptcha captcha = new SpecCaptcha(130, 48, 5);
captcha.setCharType(Captcha.TYPE_ONLY_NUMBER);

只有SpecCaptchaGifCaptcha设置才有效果。

5.3.字体设置

内置字体:

字体 效果
Captcha.FONT_1
Captcha.FONT_2
Captcha.FONT_3
Captcha.FONT_4
Captcha.FONT_5
Captcha.FONT_6
Captcha.FONT_7
Captcha.FONT_8
Captcha.FONT_9
Captcha.FONT_10

使用方法:

SpecCaptcha captcha = new SpecCaptcha(130, 48, 5);

// 设置内置字体
captcha.setFont(Captcha.FONT_1); 

// 设置系统字体
captcha.setFont(new Font("楷体", Font.PLAIN, 28)); 

5.4.输出base64编码

SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
specCaptcha.toBase64();

// 如果不想要base64的头部data:image/png;base64,
specCaptcha.toBase64("");  // 加一个空的参数即可

5.5.输出到文件

FileOutputStream outputStream = new FileOutputStream(new File("C:/captcha.png"))
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
specCaptcha.out(outputStream);

6.前后端分离项目的使用

 前后端分离项目建议不要存储在session中,存储在redis中,redis存储需要一个key,key一同返回给前端用于验证输入:

@Controller
public class CaptchaController {
    @Autowired
    private RedisUtil redisUtil;
    
    @ResponseBody
    @RequestMapping("/captcha")
    public JsonResult captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
        String verCode = specCaptcha.text().toLowerCase();
        String key = UUID.randomUUID().toString();
        // 存入redis并设置过期时间为30分钟
        redisUtil.setEx(key, verCode, 30, TimeUnit.MINUTES);
        // 将key和base64返回给前端
        return JsonResult.ok().put("key", key).put("image", specCaptcha.toBase64());
    }
    
    @ResponseBody
    @PostMapping("/login")
    public JsonResult login(String username,String password,String verCode,String verKey){
        // 获取redis中的验证码
        String redisCode = redisUtil.get(verKey);
        // 判断验证码
        if (verCode==null || !redisCode.equals(verCode.trim().toLowerCase())) {
            return JsonResult.error("验证码不正确");
        }
    }  
}

前端使用ajax获取验证码:

<img id="verImg" width="130px" height="48px"/>

<script>
    var verKey;
    // 获取验证码
    $.get('/captcha', function(res) {
        verKey = res.key;
        $('#verImg').attr('src', res.image);
    },'json');
    
    // 登录
    $.post('/login', {
        verKey: verKey,
        verCode: '8u6h',
        username: 'admin'
        password: 'admin'
    }, function(res) {
        console.log(res);
    }, 'json');
</script>

RedisUtil到这里获取https://gitee.com/whvse/RedisUtil


7.自定义效果

 继承Captcha实现out方法,中文验证码可继承ChineseCaptchaAbstract,算术验证码可继承ArithmeticCaptchaAbstract


8.更新日志

  • 2019-08-23 (v1.6.2)

    • 增加10种漂亮的内置字体,不依赖系统字体

    • 增加算术验证码,运算位数可自由配置

    • 增加输出base64编码的功能

    • 增加贝塞尔曲线作为干扰线

  • 2018-08-09 (v1.5.0)

    • 增加纯大写字母、纯小写字母、数字和大写字母配置

    • 增加中文验证码、中文gif验证码

    • 增加抗锯齿效果,优化文字颜色

    • 增加CaptchaUtil便于Web项目使用

easycaptcha's People

Contributors

dependabot[bot] avatar dhb52 avatar ele-admin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

easycaptcha's Issues

specCaptcha.setFont 报错 java.util.zip.ZipException: invalid code lengths set

java.util.zip.ZipException: invalid code lengths set at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:164) at org.springframework.boot.loader.jar.ZipInflaterInputStream.read(ZipInflaterInputStream.java:52) at java.io.FilterInputStream.read(FilterInputStream.java:107) at java.awt.Font.createFont0(Font.java:936) at java.awt.Font.createFont(Font.java:877) at com.wf.captcha.base.Captcha.setFont(Captcha.java:277) at com.wf.captcha.base.Captcha.setFont(Captcha.java:273) at net.hopemobi.put.manage.api.controller.CaptchaController.getCode(CaptchaController.java:44)

原代码为
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4); specCaptcha.setFont(Captcha.FONT_1, 38);

在docker中运行报错java.io.IOException: Problem reading font data.

java.io.IOException: Problem reading font data.
at java.awt.Font.createFont0(Font.java:1000) ~[?:1.8.0_111-internal]
at java.awt.Font.createFont(Font.java:877) ~[?:1.8.0_111-internal]
at com.wf.captcha.base.Captcha.setFont(Captcha.java:277) ~[easy-captcha-1.6.2.jar!/:?]
at com.wf.captcha.base.Captcha.setFont(Captcha.java:273) ~[easy-captcha-1.6.2.jar!/:?]
at com.wf.captcha.base.Captcha.setFont(Captcha.java:269) ~[easy-captcha-1.6.2.jar!/:?]

docker里没有字体?

一点建议

我有个不成熟的想法
其实可以使用注解的方式来注入到spring里面,比如说

@SendCaptcha
@GetMapping
public void captcha(@CaptchaValue String code) {
    return Result.ok(code);
}

另外作者可以贴一个收款码,我略尽点微薄之力
还可以试着申请一下idea的开源项目认证,可以拿一个正版的许可证

尝试GraalVM编译native image的时候不支持。

有可能换个实现,不依赖java.awt.image吗?我们程序用的quarkus.io,他可以使用GraalVM编译成本地镜像,降低memory的footprint,提高启动速度等。但是,尝试整合的时候,发现没有java.awt.image的支持。

Caused by: java.lang.UnsupportedOperationException: Not implemented yet for GraalVM native images
        at java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(GraphicsEnvironment.java:15)
        at java.awt.image.BufferedImage.createGraphics(BufferedImage.java:1181)
        at java.awt.image.BufferedImage.getGraphics(BufferedImage.java:1170)
        at com.wf.captcha.SpecCaptcha.graphicsImage(SpecCaptcha.java:63)
        at com.wf.captcha.SpecCaptcha.out(SpecCaptcha.java:45)
        at net.kaiba.blueeyes.captcha.CaptchaResource.generate(CaptchaResource.java:35)
        at java.lang.reflect.Method.invoke(Method.java:566)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:167)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
        at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:621)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:487)
        at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:437)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:362)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:439)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:400)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:374)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:67)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:488)
        ... 19 more

部分符号无法展示

之前我也写了一个类似的验证码工具,想参考下作者的漂亮字体,测试算术计算验证码发现有部分字体无法展示+、-、=符号

在docker里运行失败

描述

  1. 正常调试无问题

  2. 打包成 docker 后,报空指针

docker file

FROM frolvlad/alpine-oraclejdk8:slim
# 包与运行
RUN mkdir /data
ADD ./target/mini-admin-0.0.1-SNAPSHOT.jar /data/app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "-server", "-Xmx2048m", "-Xms2048m","-Dspring.profiles.active=prod", "/data/app.jar"]

Error

2020-04-12T15:56:43.381351900Z 2020-04-12 23:56:43.380 ERROR 1 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[.[dispatcherServlet]      : Servlet.service() for servlet [dispatcherServlet] in context with path [/api] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
2020-04-12T15:56:43.381417400Z
2020-04-12T15:56:43.381439100Z java.lang.NullPointerException: null
2020-04-12T15:56:43.381459400Z 	at com.wf.captcha.base.ArithmeticCaptchaAbstract.alphas(ArithmeticCaptchaAbstract.java:42) ~[easy-captcha-1.6.2.jar!/:na]
2020-04-12T15:56:43.381479900Z 	at com.wf.captcha.base.Captcha.checkAlpha(Captcha.java:156) ~[easy-captcha-1.6.2.jar!/:na]
2020-04-12T15:56:43.381500000Z 	at com.wf.captcha.base.Captcha.text(Captcha.java:137) ~[easy-captcha-1.6.2.jar!/:na]

....

在华为云的鲲鹏机器上,生成的验证码会产生失真。更神奇的是,只有这一台机器会产生失真。别的机器都不会失真

image
image
image

系统信息:

Linux hadoop1 4.18.0-80.7.2.el7.aarch64 #1 SMP Thu Sep 12 16:13:20 UTC 2019 aarch64 aarch64 aarch64 GNU/Linux

Java 信息:

java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)

代码片段:

       SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);
        CaptchaDto captchaDto = new CaptchaDto();
        captchaDto.setImage(specCaptcha.toBase64());

javax.imageio.IIOException: I/O error writing PNG file!

报错 :javax.imageio.IIOException: I/O error writing PNG file!

/** * 创建图片 * * @param captchaId(图片) * @return */ @SneakyThrows public BufferedImage createImageCaptcha(String captchaId) { Assert.hasText(captchaId, "captchaId不能为空"); String key = generateImageCacheKey(captchaId); // 三个参数分别为宽、高、位数 Captcha captcha = new SpecCaptcha(130, 48, 5); // 设置字体 // captcha1.setFont(new Font("Verdana", Font.PLAIN, 32)); // 有默认字体,可以不用设置 redisTemplate.opsForValue().set(key, captcha.text()); redisTemplate.expire(key, IMAGE_EXPIRE_MINUTE, TimeUnit.MINUTES); ByteArrayOutputStream baos = new ByteArrayOutputStream(); captcha.out(baos); byte[] data = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(data); BufferedImage image = ImageIO.read(bais); baos.close(); bais.close(); return image; }

下次更新版本时,希望能保留一下旧的版本在maven**仓库中

我原本使用

            <dependency>
                <groupId>com.github.whvcse</groupId>
                <artifactId>easyCaptcha</artifactId>
                <version>1.5.0</version>
            </dependency>

现在**仓库已经没有这个包了。(https://repo1.maven.org/maven2/com/github/whvcse/)

每次一联网打包,就报错了

[ERROR] Failed to execute goal on project mvc: Could not resolve dependencies for project : Could not find artifact com.github.whvcse:EasyCaptcha:jar:1.5.0 in central (http://repo.maven.apache.org/maven2) -> [Help 1]

希望下次能注意一下,同时感谢您给我们带来如此方便好用的工具

放在服务器验证码是乱码

在本地测试正常显示,但是放在服务器,验证码就乱了。不是字符乱码的问题,因为只有数字和字母,用的也是默认字体。感觉是在渲染干扰线的时候,就乱了。。。这个怎么解决呢?

docker使用openjdk:8-jre-alpine环境的时候缺少字体

java.lang.NullPointerException
at sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1264)
at sun.awt.FontConfiguration.readFontConfigFile(FontConfiguration.java:219)
at sun.awt.FontConfiguration.init(FontConfiguration.java:107)
at sun.awt.X11FontManager.createFontConfiguration(X11FontManager.java:774)
at sun.font.SunFontManager$2.run(SunFontManager.java:431)
at java.security.AccessController.doPrivileged(Native Method)
at sun.font.SunFontManager.(SunFontManager.java:376)
at sun.awt.FcFontManager.(FcFontManager.java:35)
at sun.awt.X11FontManager.(X11FontManager.java:57)
at sun.reflect.GeneratedConstructorAccessor70.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.lang.Class.newInstance(Class.java:442)
at sun.font.FontManagerFactory$1.run(FontManagerFactory.java:83)
at java.security.AccessController.doPrivileged(Native Method)
at sun.font.FontManagerFactory.getInstance(FontManagerFactory.java:74)
at sun.font.SunFontManager.getInstance(SunFontManager.java:250)
at sun.font.FontDesignMetrics.getMetrics(FontDesignMetrics.java:264)
at sun.java2d.SunGraphics2D.getFontMetrics(SunGraphics2D.java:856)
at com.wf.captcha.GifCaptcha.graphicsImage(GifCaptcha.java:116)
at com.wf.captcha.GifCaptcha.out(GifCaptcha.java:65)
at com.wf.captcha.base.Captcha.toBase64(Captcha.java:127)
at com.wf.captcha.GifCaptcha.toBase64(GifCaptcha.java:85)

升级springboot3 验证码升级

作者你好,很喜欢你这个验证码,但是现在系统升级到了springboot3 ,里面没有javax这个包了,所以你可不可也升级一下,兼容springboot3版本呀

修改长度后不能验证

修改验证码长度为4位时,这里的顺序好像会导致返回前端的是4位,但服务端存储的还是5位
image

支持的Java版本

作者你好,谢谢你贡献这个成果!在查看使用文档的时候,我发现没有提及该库支持的Java版本,我想知道它支持JDK 11吗?因为之前我使用过一个很老的验证码库,它在linux系统的JDK 11环境下会因为字体文件问题出错。

算数验证码中生成结果部分为整形,部分为浮点型

对算数型验证码进行测试:
for (int i = 0; i < 100; i++) { Captcha captcha = new ArithmeticCaptcha(111, 36, 3); System.out.println(captcha.text()); }
测试的结果中,少部分结果是浮点型,这样前端输验证码是肯定匹配错误的
7.0
1
-10
2.0
24
16
7
-4.0

验证验证码时,是不是需要将验证码失效?

	/**
	 * 验证验证码,用于分离的项目
	 */
	public boolean ver(String key, String code, HttpServletRequest rq) {
		ServletContext sc = rq.getServletContext();
		String keyName = codeName + "-" + key;
		String captcha = (String) sc.getAttribute(keyName);
		return code.equals(captcha);
	}

是不是应该有sc.removeAttribute(keyName);操作

生成数字图片验证报ArrayIndexOutOfBoundsException (版本: 1.5.0)

jdk版本:openjdk 1.8.0_102
easyCaptcha版本: 1.5.0

生成数字图片验证报ArrayIndexOutOfBoundsException (版本: 1.5.0)

具体代码如下:
SpecCaptcha specCaptcha = new SpecCaptcha(120, 60, 4); specCaptcha.setCharType(Captcha.TYPE_ONLY_NUMBER); String code = specCaptcha.text(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); specCaptcha.out(outputStream); String png = Algorithm.base64Encode(outputStream.toByteArray());

堆栈信息如下,请问这个是什么问题?
错误堆栈

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.