原创,转载请注明出处!

最开始用图形来模仿文字进行各种角度的倒立和排列,后来切换为文字后,有很多问题。总结如下:

1、程序在画图形和画文字方面不一样,图形的是从原点开始(0,0),而文字则从文字的基线开始(0,baseline)

2、在增加角度偏移时,文字或图形的高宽会产生变化(偏∠45度时达到最大),这时候为了让它们顶点对齐,需要计算偏移量(用三角函数)

3、在绘图时,会先旋转“画布”(描述可能不准确),再绘制文字。此时要往回旋转,否则下一个图形会顺着这个角度继续画。

4、为了让图形保持固定宽度,对于有偏角的文字,需要平均缩小左右间距(否则不同的角度,固定的文字个数,会让图形宽度不同)

效果图:

源码:(代码还可以再整理和优化,但限于计划时间,懒得弄了)

  1. package main
  2.  
  3. import (
  4. "math"
  5. "reflect"
  6. "time"
  7. "math/rand"
  8. "github.com/golang/freetype/truetype"
  9. "github.com/fogleman/gg" // 需要安装这个包
  10. "flag"
  11. "fmt"
  12. "io/ioutil"
  13. "log"
  14. )
  15.  
  16. var wORDSLIB = []interface{}{"赵","钱","孙","李","周","吴","郑","冯","陈","褚","卫","蒋","沈","韩","杨","朱","秦","尤","许","何","吕","施","张","孔","曹","严","华","金","魏","陶","姜","戚","谢","邹","喻","柏","水","窦","章","云","苏","潘","葛","奚","范","彭","郎","鲁","韦","昌","马","苗","凤","花","方","俞","任","袁","柳","酆","鲍","史","唐","费","廉","岑","薛","雷","贺","倪","汤","滕","殷","罗","毕","郝","邬","安","常","乐","于","时","傅","皮","卞","齐","康","伍","余","元","卜","顾","孟","平","黄","和","穆","萧","尹","姚","邵","湛","汪","祁","毛","禹","狄","米","贝","明","臧","计","伏","成","戴","谈","宋","茅","庞","熊","纪","舒","屈","项","祝","董","梁","杜","阮","蓝","闵","席","季","麻","强","贾","路","娄","危","江","童","颜","郭","梅","盛","林","刁","钟","徐","邱","骆","高","夏","蔡","樊","胡","凌","霍","虞","万","支","柯","昝","管","卢","莫","经","房","裘","缪","干","解","应","宗","丁","宣","贲","邓","郁","单","杭","洪","包","诸","左","石","崔","吉","钮","龚","程","嵇","邢","滑","裴","陆","荣","翁","荀","羊","於","惠","甄","曲","家","封","芮","羿","储","靳","汲","邴","糜","松","井","段","富","巫","乌","焦","巴","弓","牧","隗","山","谷","车","侯","宓","蓬","全","郗","班","仰","秋","仲","伊","宫","宁","仇","栾","暴","甘","钭","厉","戎","祖","武","符","刘","景","詹","束","龙","叶","幸","司","韶","郜","黎","蓟","薄","印","宿","白","怀","蒲","邰","从","鄂","索","咸","籍","赖","卓","蔺","屠","蒙","池","乔","阴","鬱","胥","能","苍","双","闻","莘","党","翟","谭","贡","劳","逄","姬","扶","堵","冉","宰","郦","雍","卻","璩","桑","桂","濮","牛","寿","通","边","扈","燕","冀","郏","浦","尚","农","温","别","庄","晏","柴","瞿","阎","充","慕","连","茹","习","宦","艾","鱼","容","向","古","易","慎","戈","廖","庾","终","暨","居","衡","步","都","耿","满","弘","匡","国","文","寇","广","禄","阙","东","欧","殳","沃","利","蔚","越","夔","隆","师","巩","厍","聂","晁","勾","敖","融","冷","訾","辛","阚","那","简","饶","空","曾","毋","沙","乜","养","鞠","须","丰","巢","关","蒯","相","查","后","荆","红","游","竺","权","逯","盖","益","桓","公","俟","上","官","阳","人","赫","皇","甫","尉","迟","澹","台","冶","政","淳","太","叔","申","轩","辕","令","狐","离","宇","长","鲜","闾","丘","徒","仉","督","子","颛","端","木","西","漆","雕","正","壤","驷","良","拓","跋","夹","父","晋","楚","闫","法","汝","鄢","涂","钦","百","里","南","门","呼","延","归","海","舌","微","生","岳","帅","缑","亢","况","郈","有","琴","商","牟","佘","佴","伯","赏","墨","哈","谯","笪","年","爱","佟","第","五","言","福","姓"}
  17.  
  18. var (
  19. dpi = flag.Float64("dpi", 72, "screen resolution in Dots Per Inch")
  20. fontfile = flag.String("fontfile", "./SIMYOU.TTF", "filename of the ttf font")
  21. size = flag.Float64("size", 40, "font size in points")
  22. )
  23.  
  24. func main() {
  25. x := "abc"
  26. fmt.Println(x[0:3])
  27. drawImageBygg()
  28. }
  29.  
  30. func drawImageBygg(){
  31. dc := gg.NewContext(320, 56) // 56 => w*sin(45) + h*sin(45) 45度时,字体达到最大高度
  32. dc.SetRGBA(1, 1, 1,0) // 设置背景色:末尾为透明度 1-0(1-不透明 0-透明)
  33. dc.Clear()
  34. dc.SetRGBA(0, 0, 0,1) // 设置字体色
  35.  
  36. fontBytes, err := ioutil.ReadFile(*fontfile)
  37. if err != nil {
  38. log.Println(err)
  39. return
  40. }
  41. font, err := truetype.Parse(fontBytes)
  42. if err != nil {
  43. log.Println(err)
  44. return
  45. }
  46. face := truetype.NewFace(font, &truetype.Options{
  47. Size: *size,
  48. DPI:*dpi,
  49. })
  50. dc.SetFontFace(face)
  51.  
  52. // 初始化用于计算坐标的变量
  53. fm := face.Metrics()
  54. ascent := float64(fm.Ascent.Round()) // 字体的基线到顶部距离
  55. decent := float64(fm.Descent.Round()) // 字体的基线到底部的距离
  56. w := float64(fm.Height.Round()) // 方块字,大多数应为等宽字,即和高度一样
  57. h := float64(fm.Height.Round())
  58. totalWidth := 0.0 // 目前已累积的图片宽度(需要用来计算字体位置)
  59.  
  60. // 随机取汉字,定位倒立的字
  61. words := getRandomMembersFromMemberLibary(wORDSLIB,8) // 取8个字
  62. reverseWordsIndex := getRandomMembersFromMemberLibary([]interface{}{0,1,2,3,4,5,6,7},2) // 随机2个倒立字
  63.  
  64. for i,word := range words{
  65. degree := If(Contain(i,reverseWordsIndex),float64(RandInt64(150,210)),float64(RandInt64(-30,30))) // 随机角度,正向角度 -30~30,倒立角度 150~210
  66. x,y,leftCutSize,rightCS := getCoordByQuadrantAndDegree(w,h,ascent,decent,degree,totalWidth)
  67. dc.RotateAbout(gg.Radians(degree),0,0)
  68. dc.DrawStringAnchored(word.(string), x,y, 0,0)
  69. dc.RotateAbout(-1*gg.Radians(degree),0,0)
  70. totalWidth = totalWidth + leftCutSize + rightCS
  71. fmt.Println("x:",x,"y:",y,"total:",totalWidth,"degree:",degree)
  72. }
  73.  
  74. dc.Stroke()
  75. dc.SavePNG("out.png")
  76. fmt.Println("Wrote out.png OK.")
  77. }
  78.  
  79. func getCoordByQuadrantAndDegree(w,h,ascent,descent,degree,beforTotalWidth float64)(x,y,leftCutSize,rightCutSize float64){
  80. var totalWidth float64
  81. switch{
  82. case degree<=0 && degree >= -40: // 第一象限:逆时针 -30度 ~ 0 <=> 330 ~ 360 (目前参数要传入负数)
  83. rd := -1 * degree // 转为正整数,便于计算
  84. leftCutSize = w*getDegreeSin(90-rd)
  85. rightCutSize = h*getDegreeSin(rd)
  86.  
  87. offset := (leftCutSize + rightCutSize - w) / 2 // 横向偏移量(角度倾斜越厉害,占宽越多,通过偏移量分摊给它的左右边距来收窄)
  88. leftCutSize,rightCutSize = leftCutSize - offset,rightCutSize - offset
  89.  
  90. totalWidth = beforTotalWidth + leftCutSize
  91. x = getDegreeSin(90 - rd)*totalWidth - w
  92. y = ascent + getDegreeSin(rd)*totalWidth
  93. case degree >=0 && degree <= 40: // 第四象限:顺时针 0 ~ 30度
  94. leftCutSize = h*getDegreeSin(degree)
  95. rightCutSize = w*getDegreeSin(90-degree)
  96.  
  97. offset := (leftCutSize + rightCutSize - w) / 2
  98. leftCutSize,rightCutSize = leftCutSize - offset,rightCutSize - offset
  99.  
  100. totalWidth = beforTotalWidth + leftCutSize // 现在totalwidth = 前面的宽 + 自己的左切边
  101. x = getDegreeSin(90-degree)*totalWidth
  102. y = ascent - getDegreeSin(degree)*totalWidth
  103. case degree >= 180 && degree <= 220: // 第二象限:顺时针 180 ~ 210度
  104. rd := degree - 180
  105. leftCutSize = h*getDegreeSin(rd)
  106. rightCutSize = w*getDegreeSin(90-rd)
  107.  
  108. offset := (leftCutSize + rightCutSize - w) / 2
  109. leftCutSize,rightCutSize = leftCutSize - offset,rightCutSize - offset
  110.  
  111. totalWidth = beforTotalWidth + leftCutSize
  112. x = -1 * (getDegreeSin(90-rd)*totalWidth + w)
  113. y = getDegreeSin(rd)*totalWidth - descent
  114. case degree >= 140 && degree <= 180: // 第三象限:顺时针 150 ~ 180度
  115. rd := 180-degree
  116. leftCutSize = w*getDegreeSin(90-rd)
  117. rightCutSize = h*getDegreeSin(rd)
  118.  
  119. offset := (leftCutSize + rightCutSize - w) / 2
  120. leftCutSize,rightCutSize = leftCutSize - offset,rightCutSize - offset
  121.  
  122. totalWidth = beforTotalWidth + leftCutSize
  123. x = -1 * (getDegreeSin(90-rd) * totalWidth)
  124. y = -1 * (getDegreeSin(rd) * totalWidth + descent)
  125. default: panic(fmt.Sprintf("非法的参数:%f",degree))
  126. }
  127. return
  128. }
  129.  
  130. func getDegreeSin(degree float64) float64{
  131. return math.Sin(degree*math.Pi/180)
  132. }
  133.  
  134. //RandInt64 ...
  135. func RandInt64(min, max int64) int64 {
  136. if min >= max || min == 0 || max == 0 {
  137. return max
  138. }
  139. rand.Seed(time.Now().UnixNano())
  140. return rand.Int63n(max-min) + min
  141. }
  142.  
  143. func getRandomMembersFromMemberLibary(lib []interface{},size int)[]interface{}{
  144. source,result := make([]interface{},0),make([]interface{},0)
  145. if size <= 0 || len(lib) == 0{
  146. return result
  147. }
  148. for _,v := range lib{
  149. source = append(source,v)
  150. }
  151. if size >= len(lib){
  152. return source
  153. }
  154. for i:=0;i<size;i++{
  155. rand.Seed(time.Now().UnixNano())
  156. pos := rand.Intn(len(source))
  157. result = append(result,source[pos])
  158. source = append(source[:pos], source[pos+1:]...)
  159. }
  160. return result
  161. }
  162.  
  163. //Contain ...
  164. func Contain(obj interface{}, target interface{}) bool {
  165. targetValue := reflect.ValueOf(target)
  166. switch reflect.TypeOf(target).Kind() {
  167. case reflect.Slice, reflect.Array:
  168. for i := 0; i < targetValue.Len(); i++ {
  169. if targetValue.Index(i).Interface() == obj {
  170. return true
  171. }
  172. }
  173. case reflect.Map:
  174. if targetValue.MapIndex(reflect.ValueOf(obj)).IsValid() {
  175. return true
  176. }
  177. }
  178.  
  179. return false
  180. }
  181.  
  182. //If ...
  183. func If(expr bool,trueVal float64,falseVal float64) float64{
  184. if expr {
  185. return trueVal
  186. }
  187. return falseVal
  188. }

  

需要一个字体文件,这里使用的幼圆(幼圆免费,其他免费字体懒得找)。

注意:字体不同,宽度和高度可能会需要调整。

golang 防知乎 中文验证码 源码的更多相关文章

  1. 关于Solr搜索标点与符号的中文分词你必须知道的(mmseg源码改造)

    关于Solr搜索标点与符号的中文分词你必须知道的(mmseg源码改造) 摘要:在中文搜索中的标点.符号往往也是有语义的,比如我们要搜索“C++”或是“C#”,我们不希望搜索出来的全是“C”吧?那样对程 ...

  2. 技本功丨知否知否,Redux源码竟如此意味深长(上集)

    夫 子 说 元月二号欠下袋鼠云技术公号一篇关于Redux源码解读的文章,转眼月底,期间常被“债主”上门催债.由于年底项目工期比较紧,于是债务就这样被利滚利.但是好在这段时间有点闲暇,于是赶紧把这篇文章 ...

  3. golang 移动应用例子 example/basic 源码框架分析

    条件编译 我们在源码中可以看到2个文件: main.go 和 main_x.go 这两个包名都是 package main , 都有 main 函数. 不会冲突么? 答案是不会的, main_x.go ...

  4. 技本功丨知否知否,Redux源码竟如此意味深长(下集)

    上集回顾 Redux是如何使用的?首先再来回顾一下这个使用demo(谁让这段代码完整地展示了redux的使用) 如果有小伙伴对这段代码不是很理解的话,建议先去学习Redux的使用再来看这篇源码,这样更 ...

  5. 一个好看的php验证码源码

    <?php     $w = 80; //设置图片宽和高 $h = 26; $str = Array(); //用来存储随机码 $string = "ABCDEFGHIJKLMNOPQ ...

  6. iOS Reactivecocoa(RAC)知其所以然(源码分析,一篇足以)

    前言 如今RAC大行其道,对其讲解的博客也多不胜数,稍微有点经验的估计也已经对这个爽到不要不要的框架运用自如了,真正沉下来研究其实现原理的估计也不在少数,这里仅仅是记录一下自己的分析理解,更是在写这篇 ...

  7. 2014年6月份第2周51Aspx源码发布详情

    AMX高效自定义分页控件(WinForm)源码  2014-6-9 [VS2008]2014.6.9更新内容:   1. 更改用户自定义分页控件功能布局.大大精简了调用分页自定义控件的代码,和使用系统 ...

  8. 轻量级验证码生成插件webutil-licenseImage源码与实例应用

    webutil-licenseImage 插件内置4种验证码样式,支持用户扩展.自定义样式实现简单验证码. 源码脱管地址: http://code.google.com/p/licenseimage/ ...

  9. android翻译应用、地图轨迹、视频广告、React Native知乎日报、网络请求框架等源码

    Android精选源码 android实现高德地图轨迹效果源码 使用React Native(Android和iOS)实现的 知乎日报效果源码 一款整合百度翻译api跟有道翻译api的翻译君 RxEa ...

随机推荐

  1. 虚拟化明星——深挖轻量级容器docker

    docker是一个轻量级容器,属于操作系统层面的虚拟化技术,封装了文件系统(AUFS)以及网络互联,进程隔离等特性. 传统虚拟化架构: docker虚拟化架构: 可以看出,docker是没有Guest ...

  2. redis集群与分片(2)-Redis Cluster集群的搭建与实践

    Redis Cluster集群 一.redis-cluster设计 Redis集群搭建的方式有多种,例如使用zookeeper等,但从redis 3.0之后版本支持redis-cluster集群,Re ...

  3. 17.async 函数

    async 函数 async 函数 含义 ES2017 标准引入了 async 函数,使得异步操作变得更加方便. async 函数是什么?一句话,它就是 Generator 函数的语法糖. 前文有一个 ...

  4. Jquery Easy UI初步学习(三)数据增删改

    第二篇只是学了加载用datagrid加载数据,数据的增删改还没有做,今天主要是解决这个问题了. 在做增删改前需要弹出对应窗口,这就需要了解一下EasyUi的弹窗控件. 摘自:http://philoo ...

  5. 【转】Java高并发,如何解决,什么方式解决

    原文地址:https://www.cnblogs.com/lr393993507/p/5909804.html 对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了. ...

  6. Linux学习6-Linux常用命令(2)

    目录处理命令     命令名称:mkdir 命令英文原意:make directories 命令所在路径:/bin/mkdir 执行权限:所有用户 功能描述:创建新目录 语法:mkdir -p[目录名 ...

  7. QT5.4.2静态编译(包含QtWebKit),在VS2013上创建项目,并成功运行

            Qt项目发布的exe程序,默认是以动态链接形式的,这样发布后的程序会附带好多Qt自己的dll库,这样文件数量不仅多,而且移植到其他电脑上的时候,还可能会遇到,提示缺其他库等各种运行不起 ...

  8. Python Django 获取表单数据的三种方式

    # In viewsdef zbsservice(request): #返回一个列表 v1 = models.Business.objects.all() # .value返回一个字典 v2 = mo ...

  9. Python逐行读取文件内容

    更详细的文件按行读取操作可以参考:http://www.cnblogs.com/xuxn/archive/2011/07/27/read-a-file-with-python.html 一行一行得从文 ...

  10. html基础-html简介-第一个网页(1)

    今天刚刚开通博客园,把我最近整理的html/css来说一下,对于初学者还是有一定的帮助. 一.先来为大家简单普及以下html (1).html英文即:hypertext markup language ...