github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/captcha/captcha.go (about)

     1  package captcha
     2  
     3  import (
     4  	"bytes"
     5  	crand "crypto/rand"
     6  	"image"
     7  	"image/color"
     8  	"image/png"
     9  	"io"
    10  	"math"
    11  	"math/rand"
    12  	"time"
    13  )
    14  
    15  const (
    16  	fontWidth  = 11
    17  	fontHeight = 18
    18  	blackChar  = 1
    19  
    20  	// Standard width and height of a captcha image.
    21  	stdWidth  = 240
    22  	stdHeight = 80
    23  
    24  	// Maximum absolute skew factor of a single digit.
    25  	maxSkew = 0.7
    26  
    27  	// Number of background circles.
    28  	circleCount = 20
    29  )
    30  
    31  var font = [][]byte{
    32  	{ // 0
    33  		0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
    34  		0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
    35  		0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
    36  		0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
    37  		1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
    38  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
    39  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
    40  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
    41  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
    42  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
    43  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
    44  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
    45  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
    46  		1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
    47  		0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
    48  		0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
    49  		0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
    50  		0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
    51  	},
    52  	{ // 1
    53  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    54  		0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    55  		0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
    56  		0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    57  		0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0,
    58  		0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
    59  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    60  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    61  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    62  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    63  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    64  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    65  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    66  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    67  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    68  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    69  		0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    70  		0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    71  	},
    72  	{ // 2
    73  		0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
    74  		0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
    75  		1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
    76  		0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
    77  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
    78  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
    79  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
    80  		0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
    81  		0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
    82  		0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
    83  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    84  		0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    85  		0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
    86  		0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
    87  		0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
    88  		0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
    89  		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    90  		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    91  	},
    92  	{ // 3
    93  		0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
    94  		1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
    95  		1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
    96  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
    97  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
    98  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
    99  		0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
   100  		0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
   101  		0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
   102  		0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
   103  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
   104  		0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   105  		0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   106  		0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   107  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
   108  		1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
   109  		1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
   110  		0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
   111  	},
   112  	{ // 4
   113  		0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
   114  		0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
   115  		0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
   116  		0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
   117  		0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
   118  		0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
   119  		0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
   120  		0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
   121  		0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
   122  		0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
   123  		1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
   124  		1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
   125  		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
   126  		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
   127  		0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
   128  		0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
   129  		0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
   130  		0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
   131  	},
   132  	{ // 5
   133  		0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
   134  		0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
   135  		0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
   136  		0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
   137  		0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
   138  		0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
   139  		0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
   140  		0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
   141  		0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
   142  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
   143  		0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   144  		0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   145  		0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   146  		0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   147  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
   148  		1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
   149  		1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
   150  		0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
   151  	},
   152  	{ // 6
   153  		0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
   154  		0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
   155  		0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
   156  		0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
   157  		0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
   158  		0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
   159  		1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
   160  		1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
   161  		1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
   162  		1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
   163  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   164  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   165  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   166  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   167  		0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
   168  		0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
   169  		0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
   170  		0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
   171  	},
   172  	{ // 7
   173  		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
   174  		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
   175  		1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
   176  		1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
   177  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
   178  		0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
   179  		0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
   180  		0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
   181  		0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
   182  		0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
   183  		0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
   184  		0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
   185  		0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
   186  		0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
   187  		0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
   188  		0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
   189  		0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
   190  		0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
   191  	},
   192  	{ // 8
   193  		0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
   194  		0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
   195  		0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
   196  		0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
   197  		0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
   198  		0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
   199  		0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
   200  		0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
   201  		0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
   202  		0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
   203  		0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
   204  		1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
   205  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   206  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   207  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   208  		1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
   209  		0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
   210  		0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
   211  	},
   212  	{ // 9
   213  		0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
   214  		0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
   215  		0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
   216  		1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
   217  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   218  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   219  		1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   220  		1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
   221  		0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
   222  		0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
   223  		0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
   224  		0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
   225  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
   226  		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
   227  		0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
   228  		0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
   229  		0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
   230  		0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
   231  	},
   232  }
   233  
   234  type Image struct {
   235  	*image.Paletted
   236  	numWidth  int //a digit Width
   237  	numHeight int //a digit Height
   238  	dotSize   int
   239  }
   240  
   241  func init() {
   242  	rand.Seed(int64(time.Second))
   243  }
   244  
   245  // randIntn returns a pseudorandom non-negative int in range [0, n).
   246  func randIntn(n int) int {
   247  	return rand.Intn(n)
   248  }
   249  
   250  // rnd returns a random number in range [from, to].
   251  func randInt(from, to int) int {
   252  	return rand.Intn(to+1-from) + from
   253  }
   254  
   255  // randFloat returns a pseudorandom float64 in range [from, to].
   256  func randFloat(from, to float64) float64 {
   257  	return (to-from)*rand.Float64() + from
   258  }
   259  
   260  func randomPalette() color.Palette {
   261  	p := make([]color.Color, circleCount+1)
   262  	// Transparent color.
   263  	p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
   264  	// Primary color.
   265  	prim := color.RGBA{
   266  		uint8(randIntn(129)),
   267  		uint8(randIntn(129)),
   268  		uint8(randIntn(129)),
   269  		0xFF,
   270  	}
   271  	p[1] = prim
   272  	// Circle colors.
   273  	for i := 2; i <= circleCount; i++ {
   274  		p[i] = randomBrightness(prim, 255)
   275  	}
   276  	return p
   277  }
   278  
   279  // NewImage returns a new captcha image of the given width and height with the
   280  // given digits, where each digit must be in range 0-9.
   281  func NewImage(digits []byte, width, height int) *Image {
   282  	m := new(Image)
   283  	m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette())
   284  	m.calculateSizes(width, height, len(digits))
   285  	// Randomly position captcha inside the image.
   286  	maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize
   287  	maxy := height - m.numHeight - m.dotSize*2
   288  	var border int
   289  	if width > height {
   290  		border = height / 5
   291  	} else {
   292  		border = width / 5
   293  	}
   294  	x := randInt(border, maxx-border)
   295  	y := randInt(border, maxy-border)
   296  	// Draw digits.
   297  	for _, n := range digits {
   298  		m.drawDigit(font[n], x, y)
   299  		x += m.numWidth + m.dotSize
   300  	}
   301  	// Draw strike-through line.
   302  	m.strikeThrough()
   303  	// Apply wave distortion.
   304  	m.distort(randFloat(5, 10), randFloat(100, 200))
   305  	// Fill image with random circles.
   306  	m.fillWithCircles(circleCount, m.dotSize)
   307  	return m
   308  }
   309  
   310  // encodedPNG encodes an image to PNG and returns
   311  // the result as a byte slice.
   312  func (m *Image) encodedPNG() []byte {
   313  	var buf bytes.Buffer
   314  	if err := png.Encode(&buf, m.Paletted); err != nil {
   315  		panic(err.Error())
   316  	}
   317  	return buf.Bytes()
   318  }
   319  
   320  // WriteTo writes captcha image in PNG format into the given writer.
   321  func (m *Image) WriteTo(w io.Writer) (int64, error) {
   322  	n, err := w.Write(m.encodedPNG())
   323  	return int64(n), err
   324  }
   325  
   326  func (m *Image) calculateSizes(width, height, ncount int) {
   327  	// Goal: fit all digits inside the image.
   328  	var border int
   329  	if width > height {
   330  		border = height / 4
   331  	} else {
   332  		border = width / 4
   333  	}
   334  	// Convert everything to floats for calculations.
   335  	w := float64(width - border*2)
   336  	h := float64(height - border*2)
   337  	// fw takes into account 1-dot spacing between digits.
   338  	fw := float64(fontWidth + 1)
   339  	fh := float64(fontHeight)
   340  	nc := float64(ncount)
   341  	// Calculate the width of a single digit taking into account only the
   342  	// width of the image.
   343  	nw := w / nc
   344  	// Calculate the height of a digit from this width.
   345  	nh := nw * fh / fw
   346  	// Digit too high?
   347  	if nh > h {
   348  		// Fit digits based on height.
   349  		nh = h
   350  		nw = fw / fh * nh
   351  	}
   352  	// Calculate dot size.
   353  	m.dotSize = int(nh / fh)
   354  	// Save everything, making the actual width smaller by 1 dot to account
   355  	// for spacing between digits.
   356  	m.numWidth = int(nw) - m.dotSize
   357  	m.numHeight = int(nh)
   358  }
   359  
   360  func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
   361  	for x := fromX; x <= toX; x++ {
   362  		m.SetColorIndex(x, y, colorIdx)
   363  	}
   364  }
   365  
   366  func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) {
   367  	f := 1 - radius
   368  	dfx := 1
   369  	dfy := -2 * radius
   370  	xo := 0
   371  	yo := radius
   372  
   373  	m.SetColorIndex(x, y+radius, colorIdx)
   374  	m.SetColorIndex(x, y-radius, colorIdx)
   375  	m.drawHorizLine(x-radius, x+radius, y, colorIdx)
   376  
   377  	for xo < yo {
   378  		if f >= 0 {
   379  			yo--
   380  			dfy += 2
   381  			f += dfy
   382  		}
   383  		xo++
   384  		dfx += 2
   385  		f += dfx
   386  		m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
   387  		m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
   388  		m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
   389  		m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
   390  	}
   391  }
   392  
   393  func (m *Image) fillWithCircles(n, maxradius int) {
   394  	maxx := m.Bounds().Max.X
   395  	maxy := m.Bounds().Max.Y
   396  	for i := 0; i < n; i++ {
   397  		colorIdx := uint8(randInt(1, circleCount-1))
   398  		r := randInt(1, maxradius)
   399  		m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx)
   400  	}
   401  }
   402  
   403  func (m *Image) strikeThrough() {
   404  	maxx := m.Bounds().Max.X
   405  	maxy := m.Bounds().Max.Y
   406  	y := randInt(maxy/3, maxy-maxy/3)
   407  	amplitude := randFloat(5, 20)
   408  	period := randFloat(80, 180)
   409  	dx := 2.0 * math.Pi / period
   410  	for x := 0; x < maxx; x++ {
   411  		xo := amplitude * math.Cos(float64(y)*dx)
   412  		yo := amplitude * math.Sin(float64(x)*dx)
   413  		for yn := 0; yn < m.dotSize; yn++ {
   414  			r := randInt(0, m.dotSize)
   415  			m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
   416  		}
   417  	}
   418  }
   419  
   420  func (m *Image) drawDigit(digit []byte, x, y int) {
   421  	skf := randFloat(-maxSkew, maxSkew)
   422  	xs := float64(x)
   423  	r := m.dotSize / 2
   424  	y += randInt(-r, r)
   425  	for yo := 0; yo < fontHeight; yo++ {
   426  		for xo := 0; xo < fontWidth; xo++ {
   427  			if digit[yo*fontWidth+xo] != blackChar {
   428  				continue
   429  			}
   430  			m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
   431  		}
   432  		xs += skf
   433  		x = int(xs)
   434  	}
   435  }
   436  
   437  func (m *Image) distort(amplude float64, period float64) {
   438  	w := m.Bounds().Max.X
   439  	h := m.Bounds().Max.Y
   440  
   441  	oldm := m.Paletted
   442  	newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
   443  
   444  	dx := 2.0 * math.Pi / period
   445  	for x := 0; x < w; x++ {
   446  		for y := 0; y < h; y++ {
   447  			xo := amplude * math.Sin(float64(y)*dx)
   448  			yo := amplude * math.Cos(float64(x)*dx)
   449  			newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
   450  		}
   451  	}
   452  	m.Paletted = newm
   453  }
   454  
   455  func randomBrightness(c color.RGBA, max uint8) color.RGBA {
   456  	minc := min3(c.R, c.G, c.B)
   457  	maxc := max3(c.R, c.G, c.B)
   458  	if maxc > max {
   459  		return c
   460  	}
   461  	n := randIntn(int(max-maxc)) - int(minc)
   462  	return color.RGBA{
   463  		uint8(int(c.R) + n),
   464  		uint8(int(c.G) + n),
   465  		uint8(int(c.B) + n),
   466  		uint8(c.A),
   467  	}
   468  }
   469  
   470  func min3(x, y, z uint8) (m uint8) {
   471  	m = x
   472  	if y < m {
   473  		m = y
   474  	}
   475  	if z < m {
   476  		m = z
   477  	}
   478  	return
   479  }
   480  
   481  func max3(x, y, z uint8) (m uint8) {
   482  	m = x
   483  	if y > m {
   484  		m = y
   485  	}
   486  	if z > m {
   487  		m = z
   488  	}
   489  	return
   490  }
   491  
   492  const (
   493  	// Standard length of uniuri string to achive ~95 bits of entropy.
   494  	StdLen = 16
   495  	// Length of uniurl string to achive ~119 bits of entropy, closest
   496  	// to what can be losslessly converted to UUIDv4 (122 bits).
   497  	UUIDLen = 20
   498  )
   499  
   500  // Standard characters allowed in uniuri string.
   501  var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
   502  
   503  // New returns a new random string of the standard length, consisting of
   504  // standard characters.
   505  func New() string {
   506  	return NewLenChars(StdLen, StdChars)
   507  }
   508  
   509  // NewLen returns a new random string of the provided length, consisting of
   510  // standard characters.
   511  func NewLen(length int) string {
   512  	return NewLenChars(length, StdChars)
   513  }
   514  
   515  // NewLenChars returns a new random string of the provided length, consisting
   516  // of the provided byte slice of allowed characters (maximum 256).
   517  func NewLenChars(length int, chars []byte) string {
   518  	b := make([]byte, length)
   519  	r := make([]byte, length+(length/4)) // storage for random bytes.
   520  	clen := byte(len(chars))
   521  	maxrb := byte(256 - (256 % len(chars)))
   522  	i := 0
   523  	for {
   524  		if _, err := io.ReadFull(crand.Reader, r); err != nil {
   525  			panic("error reading from random source: " + err.Error())
   526  		}
   527  		for _, c := range r {
   528  			if c >= maxrb {
   529  				// Skip this number to avoid modulo bias.
   530  				continue
   531  			}
   532  			b[i] = chars[c%clen]
   533  			i++
   534  			if i == length {
   535  				return string(b)
   536  			}
   537  		}
   538  	}
   539  	panic("unreachable")
   540  }
   541  
   542  // func pic(w http.ResponseWriter, req *http.Request) {
   543  // 	d := make([]byte, 4)
   544  // 	s := NewLen(4)
   545  // 	ss := ""
   546  // 	d = []byte(s)
   547  // 	for v := range d {
   548  // 		d[v] %= 10
   549  // 		ss += strconv.FormatInt(int64(d[v]), 32)
   550  // 	}
   551  // 	w.Header().Set("Content-Type", "image/png")
   552  // 	NewImage(d, 100, 40).WriteTo(w)
   553  // 	fmt.Println(ss)
   554  // }
   555  
   556  // func index(w http.ResponseWriter, req *http.Request) {
   557  // 	str := "<meta charset=\"utf-8\"><h3>golang 图片验证码例子</h3><img border=\"1\" src=\"/pic\" alt=\"图片验证码\" onclick=\"this.src='/pic'\" />"
   558  // 	w.Header().Set("Content-Type", "text/html")
   559  // 	w.Write([]byte(str))
   560  // }
   561  
   562  // func main() {
   563  // 	http.HandleFunc("/pic", pic)
   564  // 	http.HandleFunc("/", index)
   565  // 	s := &http.Server{
   566  // 		Addr: 		 	":8080",
   567  // 		ReadTimeout: 	30*time.Second,
   568  // 		WriteTimeout: 	30*time.Second,
   569  // 		MaxHeaderBytes: 1 << 20,
   570  // 	}
   571  // 	s.ListenAndServe()
   572  // }