golang 防知乎 中文验证码 源码
原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處!
最開始用圖形來模仿文字進(jìn)行各種角度的倒立和排列,后來切換為文字后,有很多問題。總結(jié)如下:
1、程序在畫圖形和畫文字方面不一樣,圖形的是從原點(diǎn)開始(0,0),而文字則從文字的基線開始(0,baseline)
2、在增加角度偏移時(shí),文字或圖形的高寬會(huì)產(chǎn)生變化(偏∠45度時(shí)達(dá)到最大),這時(shí)候?yàn)榱俗屗鼈冺旤c(diǎn)對(duì)齊,需要計(jì)算偏移量(用三角函數(shù))
3、在繪圖時(shí),會(huì)先旋轉(zhuǎn)“畫布”(描述可能不準(zhǔn)確),再繪制文字。此時(shí)要往回旋轉(zhuǎn),否則下一個(gè)圖形會(huì)順著這個(gè)角度繼續(xù)畫。
4、為了讓圖形保持固定寬度,對(duì)于有偏角的文字,需要平均縮小左右間距(否則不同的角度,固定的文字個(gè)數(shù),會(huì)讓圖形寬度不同)
效果圖:
?
源碼:(代碼還可以再整理和優(yōu)化,但限于計(jì)劃時(shí)間,懶得弄了)
package mainimport ( "math""reflect""time""math/rand""github.com/golang/freetype/truetype""github.com/fogleman/gg" // 需要安裝這個(gè)包"flag" "fmt" "io/ioutil" "log" )var wORDSLIB = []interface{}{"趙","錢","孫","李","周","吳","鄭","馮","陳","褚","衛(wèi)","蔣","沈","韓","楊","朱","秦","尤","許","何","呂","施","張","孔","曹","嚴(yán)","華","金","魏","陶","姜","戚","謝","鄒","喻","柏","水","竇","章","云","蘇","潘","葛","奚","范","彭","郎","魯","韋","昌","馬","苗","鳳","花","方","俞","任","袁","柳","酆","鮑","史","唐","費(fèi)","廉","岑","薛","雷","賀","倪","湯","滕","殷","羅","畢","郝","鄔","安","常","樂","于","時(shí)","傅","皮","卞","齊","康","伍","余","元","卜","顧","孟","平","黃","和","穆","蕭","尹","姚","邵","湛","汪","祁","毛","禹","狄","米","貝","明","臧","計(jì)","伏","成","戴","談","宋","茅","龐","熊","紀(jì)","舒","屈","項(xiàng)","祝","董","梁","杜","阮","藍(lán)","閔","席","季","麻","強(qiáng)","賈","路","婁","危","江","童","顏","郭","梅","盛","林","刁","鐘","徐","邱","駱","高","夏","蔡","樊","胡","凌","霍","虞","萬","支","柯","昝","管","盧","莫","經(jīng)","房","裘","繆","干","解","應(yīng)","宗","丁","宣","賁","鄧","郁","單","杭","洪","包","諸","左","石","崔","吉","鈕","龔","程","嵇","邢","滑","裴","陸","榮","翁","荀","羊","於","惠","甄","曲","家","封","芮","羿","儲(chǔ)","靳","汲","邴","糜","松","井","段","富","巫","烏","焦","巴","弓","牧","隗","山","谷","車","侯","宓","蓬","全","郗","班","仰","秋","仲","伊","宮","寧","仇","欒","暴","甘","鈄","厲","戎","祖","武","符","劉","景","詹","束","龍","葉","幸","司","韶","郜","黎","薊","薄","印","宿","白","懷","蒲","邰","從","鄂","索","咸","籍","賴","卓","藺","屠","蒙","池","喬","陰","鬱","胥","能","蒼","雙","聞","莘","黨","翟","譚","貢","勞","逄","姬","扶","堵","冉","宰","酈","雍","卻","璩","桑","桂","濮","牛","壽","通","邊","扈","燕","冀","郟","浦","尚","農(nóng)","溫","別","莊","晏","柴","瞿","閻","充","慕","連","茹","習(xí)","宦","艾","魚","容","向","古","易","慎","戈","廖","庾","終","暨","居","衡","步","都","耿","滿","弘","匡","國(guó)","文","寇","廣","祿","闕","東","歐","殳","沃","利","蔚","越","夔","隆","師","鞏","厙","聶","晁","勾","敖","融","冷","訾","辛","闞","那","簡(jiǎn)","饒","空","曾","毋","沙","乜","養(yǎng)","鞠","須","豐","巢","關(guān)","蒯","相","查","后","荊","紅","游","竺","權(quán)","逯","蓋","益","桓","公","俟","上","官","陽","人","赫","皇","甫","尉","遲","澹","臺(tái)","冶","政","淳","太","叔","申","軒","轅","令","狐","離","宇","長(zhǎng)","鮮","閭","丘","徒","仉","督","子","顓","端","木","西","漆","雕","正","壤","駟","良","拓","跋","夾","父","晉","楚","閆","法","汝","鄢","涂","欽","百","里","南","門","呼","延","歸","海","舌","微","生","岳","帥","緱","亢","況","郈","有","琴","商","牟","佘","佴","伯","賞","墨","哈","譙","笪","年","愛","佟","第","五","言","福","姓"}var ( dpi = flag.Float64("dpi", 72, "screen resolution in Dots Per Inch") fontfile = flag.String("fontfile", "./SIMYOU.TTF", "filename of the ttf font") size = flag.Float64("size", 40, "font size in points") )func main() { x := "abc"fmt.Println(x[0:3])drawImageBygg() } func drawImageBygg(){dc := gg.NewContext(320, 56) // 56 => w*sin(45) + h*sin(45) 45度時(shí),字體達(dá)到最大高度dc.SetRGBA(1, 1, 1,0) // 設(shè)置背景色:末尾為透明度 1-0(1-不透明 0-透明)dc.Clear()dc.SetRGBA(0, 0, 0,1) // 設(shè)置字體色fontBytes, err := ioutil.ReadFile(*fontfile) if err != nil { log.Println(err) return } font, err := truetype.Parse(fontBytes)if err != nil {log.Println(err) return }face := truetype.NewFace(font, &truetype.Options{Size: *size,DPI:*dpi,}) dc.SetFontFace(face)// 初始化用于計(jì)算坐標(biāo)的變量fm := face.Metrics()ascent := float64(fm.Ascent.Round()) // 字體的基線到頂部距離decent := float64(fm.Descent.Round()) // 字體的基線到底部的距離w := float64(fm.Height.Round()) // 方塊字,大多數(shù)應(yīng)為等寬字,即和高度一樣h := float64(fm.Height.Round())totalWidth := 0.0 // 目前已累積的圖片寬度(需要用來計(jì)算字體位置)// 隨機(jī)取漢字,定位倒立的字words := getRandomMembersFromMemberLibary(wORDSLIB,8) // 取8個(gè)字reverseWordsIndex := getRandomMembersFromMemberLibary([]interface{}{0,1,2,3,4,5,6,7},2) // 隨機(jī)2個(gè)倒立字for i,word := range words{degree := If(Contain(i,reverseWordsIndex),float64(RandInt64(150,210)),float64(RandInt64(-30,30))) // 隨機(jī)角度,正向角度 -30~30,倒立角度 150~210x,y,leftCutSize,rightCS := getCoordByQuadrantAndDegree(w,h,ascent,decent,degree,totalWidth)dc.RotateAbout(gg.Radians(degree),0,0)dc.DrawStringAnchored(word.(string), x,y, 0,0)dc.RotateAbout(-1*gg.Radians(degree),0,0)totalWidth = totalWidth + leftCutSize + rightCSfmt.Println("x:",x,"y:",y,"total:",totalWidth,"degree:",degree)}dc.Stroke()dc.SavePNG("out.png")fmt.Println("Wrote out.png OK.") }func getCoordByQuadrantAndDegree(w,h,ascent,descent,degree,beforTotalWidth float64)(x,y,leftCutSize,rightCutSize float64){var totalWidth float64switch{case degree<=0 && degree >= -40: // 第一象限:逆時(shí)針 -30度 ~ 0 <=> 330 ~ 360 (目前參數(shù)要傳入負(fù)數(shù)) rd := -1 * degree // 轉(zhuǎn)為正整數(shù),便于計(jì)算leftCutSize = w*getDegreeSin(90-rd)rightCutSize = h*getDegreeSin(rd)offset := (leftCutSize + rightCutSize - w) / 2 // 橫向偏移量(角度傾斜越厲害,占寬越多,通過偏移量分?jǐn)偨o它的左右邊距來收窄)leftCutSize,rightCutSize = leftCutSize - offset,rightCutSize - offsettotalWidth = beforTotalWidth + leftCutSize x = getDegreeSin(90 - rd)*totalWidth - w y = ascent + getDegreeSin(rd)*totalWidth case degree >=0 && degree <= 40: // 第四象限:順時(shí)針 0 ~ 30度leftCutSize = h*getDegreeSin(degree)rightCutSize = w*getDegreeSin(90-degree)offset := (leftCutSize + rightCutSize - w) / 2leftCutSize,rightCutSize = leftCutSize - offset,rightCutSize - offsettotalWidth = beforTotalWidth + leftCutSize // 現(xiàn)在totalwidth = 前面的寬 + 自己的左切邊x = getDegreeSin(90-degree)*totalWidthy = ascent - getDegreeSin(degree)*totalWidth case degree >= 180 && degree <= 220: // 第二象限:順時(shí)針 180 ~ 210度rd := degree - 180leftCutSize = h*getDegreeSin(rd)rightCutSize = w*getDegreeSin(90-rd)offset := (leftCutSize + rightCutSize - w) / 2 leftCutSize,rightCutSize = leftCutSize - offset,rightCutSize - offsettotalWidth = beforTotalWidth + leftCutSize x = -1 * (getDegreeSin(90-rd)*totalWidth + w)y = getDegreeSin(rd)*totalWidth - descentcase degree >= 140 && degree <= 180: // 第三象限:順時(shí)針 150 ~ 180度rd := 180-degreeleftCutSize = w*getDegreeSin(90-rd)rightCutSize = h*getDegreeSin(rd)offset := (leftCutSize + rightCutSize - w) / 2leftCutSize,rightCutSize = leftCutSize - offset,rightCutSize - offsettotalWidth = beforTotalWidth + leftCutSize x = -1 * (getDegreeSin(90-rd) * totalWidth)y = -1 * (getDegreeSin(rd) * totalWidth + descent)default: panic(fmt.Sprintf("非法的參數(shù):%f",degree))}return }func getDegreeSin(degree float64) float64{return math.Sin(degree*math.Pi/180) }//RandInt64 ... func RandInt64(min, max int64) int64 {if min >= max || min == 0 || max == 0 {return max}rand.Seed(time.Now().UnixNano())return rand.Int63n(max-min) + min }func getRandomMembersFromMemberLibary(lib []interface{},size int)[]interface{}{source,result := make([]interface{},0),make([]interface{},0)if size <= 0 || len(lib) == 0{return result}for _,v := range lib{source = append(source,v)}if size >= len(lib){return source}for i:=0;i<size;i++{rand.Seed(time.Now().UnixNano())pos := rand.Intn(len(source))result = append(result,source[pos])source = append(source[:pos], source[pos+1:]...)}return result }//Contain ... func Contain(obj interface{}, target interface{}) bool {targetValue := reflect.ValueOf(target)switch reflect.TypeOf(target).Kind() {case reflect.Slice, reflect.Array:for i := 0; i < targetValue.Len(); i++ {if targetValue.Index(i).Interface() == obj {return true}}case reflect.Map:if targetValue.MapIndex(reflect.ValueOf(obj)).IsValid() {return true}}return false }//If ... func If(expr bool,trueVal float64,falseVal float64) float64{if expr {return trueVal }return falseVal }
?需要一個(gè)字體文件,這里使用的幼圓(幼圓免費(fèi),其他免費(fèi)字體懶得找)。
注意:字體不同,寬度和高度可能會(huì)需要調(diào)整。
?
轉(zhuǎn)載于:https://www.cnblogs.com/Denny_Yang/p/9317538.html
總結(jié)
以上是生活随笔為你收集整理的golang 防知乎 中文验证码 源码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自下而上滚动公告栏(可悬停)
- 下一篇: 机器学习入门KNN近邻算法(一)