下面解读的验证码代码来自: http://www.oschina.net/code/snippet_173630_12006
这个验证码的效果类似如下下图:
这个验证码包含下面三层元素
- 随机大小和颜色的10个点
- 4位数字的验证码(随机偏转方向、每个点间距随机)
- 一条类似删除线的干扰线
对应的带代码注释的源文件如下:
1: package main
2:
3: import (
4: crand "crypto/rand"
5: "fmt"
6: "image"
7: "image/color"
8: "image/png"
9: "io"
10: "math/rand"
11: "net/http"
12: "strconv"
13: "time"
14: )
15:
16: const (
17: stdWidth = 100 // 固定图片宽度
18: stdHeight = 40 // 固定图片高度
19: maxSkew = 2
20: )
21:
22: // 字体常量信息
23: const (
24: fontWidth = 5 // 字体的宽度
25: fontHeight = 8 // 字体的高度
26: blackChar = 1
27: )
28:
29: // 简化期间使用的字体库
30: var font = [][]byte{
31: { // 0
32: 0, 1, 1, 1, 0,
33: 1, 0, 0, 0, 1,
34: 1, 0, 0, 0, 1,
35: 1, 0, 0, 0, 1,
36: 1, 0, 0, 0, 1,
37: 1, 0, 0, 0, 1,
38: 1, 0, 0, 0, 1,
39: 0, 1, 1, 1, 0},
40: { // 1
41: 0, 0, 1, 0, 0,
42: 0, 1, 1, 0, 0,
43: 1, 0, 1, 0, 0,
44: 0, 0, 1, 0, 0,
45: 0, 0, 1, 0, 0,
46: 0, 0, 1, 0, 0,
47: 0, 0, 1, 0, 0,
48: 1, 1, 1, 1, 1},
49: { // 2
50: 0, 1, 1, 1, 0,
51: 1, 0, 0, 0, 1,
52: 0, 0, 0, 0, 1,
53: 0, 0, 0, 1, 1,
54: 0, 1, 1, 0, 0,
55: 1, 0, 0, 0, 0,
56: 1, 0, 0, 0, 0,
57: 1, 1, 1, 1, 1},
58: { // 3
59: 1, 1, 1, 1, 0,
60: 0, 0, 0, 0, 1,
61: 0, 0, 0, 1, 0,
62: 0, 1, 1, 1, 0,
63: 0, 0, 0, 1, 0,
64: 0, 0, 0, 0, 1,
65: 0, 0, 0, 0, 1,
66: 1, 1, 1, 1, 0},
67: { // 4
68: 1, 0, 0, 1, 0,
69: 1, 0, 0, 1, 0,
70: 1, 0, 0, 1, 0,
71: 1, 0, 0, 1, 0,
72: 1, 1, 1, 1, 1,
73: 0, 0, 0, 1, 0,
74: 0, 0, 0, 1, 0,
75: 0, 0, 0, 1, 0},
76: { // 5
77: 1, 1, 1, 1, 1,
78: 1, 0, 0, 0, 0,
79: 1, 0, 0, 0, 0,
80: 1, 1, 1, 1, 0,
81: 0, 0, 0, 0, 1,
82: 0, 0, 0, 0, 1,
83: 0, 0, 0, 0, 1,
84: 1, 1, 1, 1, 0},
85: { // 6
86: 0, 0, 1, 1, 1,
87: 0, 1, 0, 0, 0,
88: 1, 0, 0, 0, 0,
89: 1, 1, 1, 1, 0,
90: 1, 0, 0, 0, 1,
91: 1, 0, 0, 0, 1,
92: 1, 0, 0, 0, 1,
93: 0, 1, 1, 1, 0},
94: { // 7
95: 1, 1, 1, 1, 1,
96: 0, 0, 0, 0, 1,
97: 0, 0, 0, 0, 1,
98: 0, 0, 0, 1, 0,
99: 0, 0, 1, 0, 0,
100: 0, 1, 0, 0, 0,
101: 0, 1, 0, 0, 0,
102: 0, 1, 0, 0, 0},
103: { // 8
104: 0, 1, 1, 1, 0,
105: 1, 0, 0, 0, 1,
106: 1, 0, 0, 0, 1,
107: 0, 1, 1, 1, 0,
108: 1, 0, 0, 0, 1,
109: 1, 0, 0, 0, 1,
110: 1, 0, 0, 0, 1,
111: 0, 1, 1, 1, 0},
112: { // 9
113: 0, 1, 1, 1, 0,
114: 1, 0, 0, 0, 1,
115: 1, 0, 0, 0, 1,
116: 1, 1, 0, 0, 1,
117: 0, 1, 1, 1, 1,
118: 0, 0, 0, 0, 1,
119: 0, 0, 0, 0, 1,
120: 1, 1, 1, 1, 0},
121: }
122:
123: type Image struct {
124: *image.NRGBA
125: color *color.NRGBA
126: width int //a digit width
127: height int //a digit height
128: dotsize int
129: }
130:
131: func init() {
132: // 打乱随机种子
133: rand.Seed(int64(time.Second))
134:
135: }
136:
137: // 产生指定长宽的图片
138: func NewImage(digits []byte, width, height int) *Image {
139: img := new(Image)
140: r := image.Rect(img.width, img.height, stdWidth, stdHeight)
141: img.NRGBA = image.NewNRGBA(r)
142: img.color = &color.NRGBA{
143: uint8(rand.Intn(129)),
144: uint8(rand.Intn(129)),
145: uint8(rand.Intn(129)),
146: 0xFF}
147:
148: img.calculateSizes(width, height, len(digits))
149:
150: // Draw background (10 random circles of random brightness)
151: // 画背景, 10个随机亮度的园
152: img.fillWithCircles(10, img.dotsize)
153: maxx := width - (img.width+img.dotsize)*len(digits) - img.dotsize
154: maxy := height - img.height - img.dotsize*2
155: x := rnd(img.dotsize*2, maxx)
156: y := rnd(img.dotsize*2, maxy)
157:
158: // Draw digits. 画验证码
159: for _, n := range digits {
160: img.drawDigit(font[n], x, y)
161: x += img.width + img.dotsize // 下一个验证码字符的起始位置
162: }
163:
164: // Draw strike-through line. 画类似删除线的干扰线
165: img.strikeThrough()
166: return img
167:
168: }
169:
170: func (img *Image) WriteTo(w io.Writer) (int64, error) {
171: return 0, png.Encode(w, img)
172:
173: }
174:
175: // 计算几个要显示字符的尺寸,没有开始绘画。
176: func (img *Image) calculateSizes(width, height, ncount int) {
177:
178: // Goal: fit all digits inside the image.
179: var border int // 边距
180: if width > height {
181: border = height / 5
182: } else {
183: border = width / 5
184: }
185: // Convert everything to floats for calculations.
186: w := float64(width - border*2) // 100 - 8*2=84
187: h := float64(height - border*2) // 40 - 8*2 = 24
188: fmt.Println("ddd%v;%v", w, h)
189:
190: // fw takes into account 1-dot spacing between digits.
191: fw := float64(fontWidth) + 1 // 6
192: fh := float64(fontHeight) // 8
193: nc := float64(ncount) // 4
194: fmt.Println("eee%v;%v;%v", fw, fh, nc)
195:
196: // Calculate the width of a single digit taking into account only the
197: // width of the image.
198: nw := w / nc // 84/ 4 = 21
199:
200: // Calculate the height of a digit from this width.
201: nh := nw * fh / fw // 21*8/6 = 28
202:
203: // Digit too high?
204: if nh > h {
205: // Fit digits based on height.
206: nh = h // nh = 24
207: nw = fw / fh * nh // 6 / 8 * 24 = 18
208: }
209:
210: // Calculate dot size.
211: img.dotsize = int(nh / fh) // 24/8 = 3
212:
213: // Save everything, making the actual width smaller by 1 dot to account
214: // for spacing between digits.
215: img.width = int(nw) // 18
216: img.height = int(nh) - img.dotsize // 24 - 3 = 21
217:
218: fmt.Printf("format:%v;%v;%v/r/n", img.dotsize, img.width, img.height)
219: }
220:
221: // 随机画指定个数个圆点
222: func (img *Image) fillWithCircles(n, maxradius int) {
223: color := img.color
224: maxx := img.Bounds().Max.X
225: maxy := img.Bounds().Max.Y
226: for i := 0; i < n; i++ {
227: setRandomBrightness(color, 255) // 随机颜色亮度
228: r := rnd(1, maxradius) // 随机大小
229: img.drawCircle(color, rnd(r, maxx-r), rnd(r, maxy-r), r)
230: }
231:
232: }
233:
234: // 画 水平线
235: func (img *Image) drawHorizLine(color color.Color, fromX, toX, y int) {
236: // 遍历画每个点
237: for x := fromX; x <= toX; x++ {
238: img.Set(x, y, color)
239: }
240:
241: }
242:
243: // 画指定颜色的实心圆
244: func (img *Image) drawCircle(color color.Color, x, y, radius int) {
245: f := 1 - radius
246: dfx := 1
247: dfy := -2 * radius
248: xx := 0
249: yy := radius
250: img.Set(x, y+radius, color)
251: img.Set(x, y-radius, color)
252: img.drawHorizLine(color, x-radius, x+radius, y)
253: for xx < yy {
254: if f >= 0 {
255: yy--
256: dfy += 2
257: f += dfy
258: }
259: xx++
260: dfx += 2
261: f += dfx
262: img.drawHorizLine(color, x-xx, x+xx, y+yy)
263: img.drawHorizLine(color, x-xx, x+xx, y-yy)
264: img.drawHorizLine(color, x-yy, x+yy, y+xx)
265: img.drawHorizLine(color, x-yy, x+yy, y-xx)
266: }
267:
268: }
269:
270: // 画一个随机干扰线
271: func (img *Image) strikeThrough() {
272: r := 0
273: maxx := img.Bounds().Max.X
274: maxy := img.Bounds().Max.Y
275: y := rnd(maxy/3, maxy-maxy/3)
276: for x := 0; x < maxx; x += r {
277: r = rnd(1, img.dotsize/3)
278: y += rnd(-img.dotsize/2, img.dotsize/2)
279: if y <= 0 || y >= maxy {
280: y = rnd(maxy/3, maxy-maxy/3)
281: }
282: img.drawCircle(img.color, x, y, r)
283: }
284:
285: }
286:
287: // 画指定的验证码其中一个字符
288: func (img *Image) drawDigit(digit []byte, x, y int) {
289: // 随机偏转方向
290: skf := rand.Float64() * float64(rnd(-maxSkew, maxSkew))
291: xs := float64(x)
292: minr := img.dotsize / 2 // minumum radius
293: maxr := img.dotsize/2 + img.dotsize/4 // maximum radius
294: y += rnd(-minr, minr)
295: for yy := 0; yy < fontHeight; yy++ {
296: for xx := 0; xx < fontWidth; xx++ {
297: if digit[yy*fontWidth+xx] != blackChar {
298: continue
299: }
300: // Introduce random variations.
301: // 引入一些随机变化,不过这里变化量非常小
302: or := rnd(minr, maxr)
303: ox := x + (xx * img.dotsize) + rnd(0, or/2)
304: oy := y + (yy * img.dotsize) + rnd(0, or/2)
305: img.drawCircle(img.color, ox, oy, or)
306: }
307: xs += skf
308: x = int(xs)
309: }
310:
311: }
312:
313: // 设置随机颜色亮度
314: func setRandomBrightness(c *color.NRGBA, max uint8) {
315: minc := min3(c.R, c.G, c.B)
316: maxc := max3(c.R, c.G, c.B)
317: if maxc > max {
318: return
319: }
320: n := rand.Intn(int(max-maxc)) - int(minc)
321: c.R = uint8(int(c.R) + n)
322: c.G = uint8(int(c.G) + n)
323: c.B = uint8(int(c.B) + n)
324:
325: }
326:
327: // 三个数中的最小数
328: func min3(x, y, z uint8) (o uint8) {
329: o = x
330: if y < o {
331: o = y
332: }
333: if z < o {
334: o = z
335: }
336: return
337:
338: }
339:
340: // 三个数的最大数
341: func max3(x, y, z uint8) (o uint8) {
342: o = x
343: if y > o {
344: o = y
345: }
346: if z > o {
347: o = z
348: }
349: return
350:
351: }
352:
353: // rnd returns a random number in range [from, to].
354: // 然会指定范围的随机数
355: func rnd(from, to int) int {
356: //println(to+1-from)
357: return rand.Intn(to+1-from) + from
358:
359: }
360:
361: const (
362: // Standard length of uniuri string to achive ~95 bits of entropy.
363: StdLen = 16
364: // Length of uniurl string to achive ~119 bits of entropy, closest
365: // to what can be losslessly converted to UUIDv4 (122 bits).
366: UUIDLen = 20
367: )
368:
369: // Standard characters allowed in uniuri string.
370: // 验证码中标准的字符 大小写与数字
371: var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
372:
373: // New returns a new random string of the standard length, consisting of
374: // standard characters.
375: func New() string {
376: return NewLenChars(StdLen, StdChars)
377:
378: }
379:
380: // NewLen returns a new random string of the provided length, consisting of standard characters.
381: // 返回指定长度的随机字符串
382: func NewLen(length int) string {
383: return NewLenChars(length, StdChars)
384:
385: }
386:
387: // NewLenChars returns a new random string of the provided length, consisting
388: // of the provided byte slice of allowed characters (maximum 256).
389: // 返回指定长度,指定候选字符的随机字符串(最大256)
390: func NewLenChars(length int, chars []byte) string {
391: b := make([]byte, length)
392: r := make([]byte, length+(length/4)) // storage for random bytes. 随机字节的存储空间, 多读几个以免
393:
394: clen := byte(len(chars))
395: maxrb := byte(256 - (256 % len(chars))) // 问题, 为什么要申请这么长的数组? 看下面循环的 continue 条件
396:
397: i := 0
398: for {
399: // rand.Read() 和 io.ReadFull(rand.Reader) 的区别?
400: // http://www.cnblogs.com/ghj1976/p/3435940.html
401: if _, err := io.ReadFull(crand.Reader, r); err != nil {
402: panic("error reading from random source: " + err.Error())
403: }
404: for _, c := range r {
405: if c >= maxrb {
406: // Skip this number to avoid modulo bias.
407: // 跳过 maxrb, 以避免麻烦,这也是随机数要多读几个的原因。
408: continue
409: }
410: b[i] = chars[c%clen]
411: i++
412: if i == length {
413: return string(b)
414: }
415: }
416: }
417: panic("unreachable")
418:
419: }
420:
421: func pic(w http.ResponseWriter, req *http.Request) {
422: // 产生验证码byte数组
423: d := make([]byte, 4)
424: s := NewLen(4)
425: d = []byte(s)
426:
427: // 把验证码变成需要显示的字符串
428: ss := ""
429: for v := range d {
430: d[v] %= 10
431: ss += strconv.FormatInt(int64(d[v]), 32)
432: }
433:
434: // 图片流方式输出
435: w.Header().Set("Content-Type", "image/png")
436: NewImage(d, 100, 40).WriteTo(w)
437:
438: // 打印出这次使用的验证码
439: fmt.Println(ss)
440: }
441:
442: func index(w http.ResponseWriter, req *http.Request) {
443: str := "<meta charset=\"utf-8\"><h3>golang 图片验证码例子</h3><img border=\"1\" src=\"/pic\" alt=\"图片验证码\" onclick=\"this.src='/pic'\" />"
444: w.Header().Set("Content-Type", "text/html")
445: w.Write([]byte(str))
446:
447: }
448:
449: func main() {
450: http.HandleFunc("/pic", pic)
451: http.HandleFunc("/", index)
452: s := &http.Server{
453: Addr: ":8080",
454: ReadTimeout: 30 * time.Second,
455: WriteTimeout: 30 * time.Second,
456: MaxHeaderBytes: 1 << 20}
457: s.ListenAndServe()
458:
459: }