package captchagen import ( "bytes" "fmt" "image" "image/color" "image/png" "io/ioutil" "log" "math/rand" "strings" "time" "github.com/fogleman/gg" ) var ImageWidth int = 600 var ImageHeight int = 400 type Logo struct { Image image.Image IsCorrect bool } type Captcha struct { Image image.Image CorrectAnswer int } var Logos []Logo = []Logo{} var XPositions []int = []int{} // Создаём пустое изображение с серым градиентом, грузим шрифт из /assets и возвращаем контекст вызвавшей функции func initImage() *gg.Context { dc := gg.NewContext(ImageWidth, ImageHeight) if err := dc.LoadFontFace("./assets/font.ttf", 24); err != nil { panic(err) } fIWidth, fIHeight := float64(ImageWidth), float64(ImageHeight) grad := gg.NewLinearGradient(0, 0, fIWidth, fIHeight) grad.AddColorStop(0, color.RGBA{71, 100, 106, 255}) grad.AddColorStop(1, color.RGBA{44, 43, 51, 255}) dc.SetFillStyle(grad) dc.DrawRectangle(0, 0, fIWidth, fIHeight) dc.Fill() return dc } // Генерация капчи. // // На пустое изображение наносятся логотипы из списка, предварительно перемешанного. // К изображениям также добавляются порядковые номера (начиная с 1 вместо 0), // а правильный вариант возвращается вместе с итоговой картинкой func GenCaptcha() Captcha { dc := initImage() rand.Seed(time.Now().UnixNano()) rand.Shuffle(len(Logos), func(i, j int) { Logos[i], Logos[j] = Logos[j], Logos[i] }) // Перемешиваем логотипы rand.Shuffle(len(XPositions), func(i, j int) { XPositions[i], XPositions[j] = XPositions[j], XPositions[i] }) // И позиции correct_answer := 0 for i, logo := range Logos { x := XPositions[i] y := rand.Intn(ImageHeight - logo.Image.Bounds().Dy() - 30) dc.DrawImage(logo.Image, x, y) if logo.IsCorrect { correct_answer = i + 1 } var tx float64 = float64(x) + 50.0 var ty float64 = float64(y) + 120.0 text := fmt.Sprintf("%d", i+1) dc.SetRGB(1, 1, 1) dc.DrawStringAnchored(text, tx, ty, 0.5, 0.5) } img := dc.Image() captcha := Captcha{ Image: img, CorrectAnswer: correct_answer, } return captcha } func (captcha *Captcha) ToReader() *bytes.Reader { buff := new(bytes.Buffer) err := png.Encode(buff, captcha.Image) if err != nil { fmt.Println("failed to create buffer", err) } reader := bytes.NewReader(buff.Bytes()) return reader } // Инициализация списка логотипов. // // Логотипы читаются из папки /assets рядом с исполняемым файлом. // Принимается формат .png, логотип, представляющий правильный ответ называется godot.png func Init() error { files, err := ioutil.ReadDir("./assets") if err != nil { return err } for _, file := range files { name := file.Name() if !strings.HasSuffix(name, ".png") { // Грузим только .png continue } log.Printf("%s", name) // Для отладки выводим имена файлов с логотипами path := fmt.Sprintf("./assets/%s", name) // Составляем путь до файла im, err := gg.LoadPNG(path) // Грузим png, возвращаем ошибку если что-то идёт не так if err != nil { log.Print(err) return err } is_correct := strings.HasPrefix(name, "godot") // Если грузимый файл -- godot*.png - помечаем его как правильный logo := Logo{Image: im, IsCorrect: is_correct} // Создаём в памяти структуру для капчи Logos = append(Logos, logo) // Заносим логотип в общий список } for i := range Logos { XPositions = append(XPositions, 50+i*(ImageWidth-50)/len(Logos)) // Горизонтальное расположение не рандомно: чтобы логотипы не перемешались. } return nil }