2022-01-28 01:00:40 +03:00
|
|
|
|
package captchagen
|
|
|
|
|
|
|
|
|
|
import (
|
2022-02-02 00:37:58 +03:00
|
|
|
|
"bytes"
|
2022-01-28 01:00:40 +03:00
|
|
|
|
"fmt"
|
|
|
|
|
"image"
|
|
|
|
|
"image/color"
|
2022-02-02 00:37:58 +03:00
|
|
|
|
"image/png"
|
2022-01-28 01:00:40 +03:00
|
|
|
|
"io/ioutil"
|
|
|
|
|
"log"
|
|
|
|
|
"math/rand"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/fogleman/gg"
|
|
|
|
|
)
|
|
|
|
|
|
2022-02-02 12:16:45 +03:00
|
|
|
|
var ImageWidth int = 600
|
|
|
|
|
var ImageHeight int = 400
|
|
|
|
|
|
2022-01-28 01:00:40 +03:00
|
|
|
|
type Logo struct {
|
|
|
|
|
Image image.Image
|
|
|
|
|
IsCorrect bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Captcha struct {
|
|
|
|
|
Image image.Image
|
|
|
|
|
CorrectAnswer int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var Logos []Logo = []Logo{}
|
2022-02-02 00:37:58 +03:00
|
|
|
|
var XPositions []int = []int{}
|
2022-01-28 01:00:40 +03:00
|
|
|
|
|
2022-01-28 16:52:26 +03:00
|
|
|
|
// Создаём пустое изображение с серым градиентом, грузим шрифт из /assets и возвращаем контекст вызвавшей функции
|
2022-01-28 01:00:40 +03:00
|
|
|
|
func initImage() *gg.Context {
|
2022-02-02 12:16:45 +03:00
|
|
|
|
dc := gg.NewContext(ImageWidth, ImageHeight)
|
2022-01-28 01:00:40 +03:00
|
|
|
|
if err := dc.LoadFontFace("./assets/font.ttf", 24); err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
2022-02-02 12:16:45 +03:00
|
|
|
|
fIWidth, fIHeight := float64(ImageWidth), float64(ImageHeight)
|
|
|
|
|
grad := gg.NewLinearGradient(0, 0, fIWidth, fIHeight)
|
2022-01-28 01:00:40 +03:00
|
|
|
|
grad.AddColorStop(0, color.RGBA{71, 100, 106, 255})
|
|
|
|
|
grad.AddColorStop(1, color.RGBA{44, 43, 51, 255})
|
|
|
|
|
|
|
|
|
|
dc.SetFillStyle(grad)
|
2022-02-02 12:16:45 +03:00
|
|
|
|
dc.DrawRectangle(0, 0, fIWidth, fIHeight)
|
2022-01-28 01:00:40 +03:00
|
|
|
|
dc.Fill()
|
|
|
|
|
return dc
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-28 16:52:26 +03:00
|
|
|
|
// Генерация капчи.
|
|
|
|
|
//
|
|
|
|
|
// На пустое изображение наносятся логотипы из списка, предварительно перемешанного.
|
|
|
|
|
// К изображениям также добавляются порядковые номера (начиная с 1 вместо 0),
|
|
|
|
|
// а правильный вариант возвращается вместе с итоговой картинкой
|
2022-01-28 01:00:40 +03:00
|
|
|
|
func GenCaptcha() Captcha {
|
|
|
|
|
dc := initImage()
|
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
2022-02-02 00:37:58 +03:00
|
|
|
|
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] }) // И позиции
|
2022-01-28 01:00:40 +03:00
|
|
|
|
correct_answer := 0
|
|
|
|
|
for i, logo := range Logos {
|
2022-02-02 00:37:58 +03:00
|
|
|
|
x := XPositions[i]
|
2022-02-02 12:16:45 +03:00
|
|
|
|
y := rand.Intn(ImageHeight - logo.Image.Bounds().Dy() - 30)
|
2022-01-28 01:00:40 +03:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-02 00:37:58 +03:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-28 16:52:26 +03:00
|
|
|
|
// Инициализация списка логотипов.
|
|
|
|
|
//
|
|
|
|
|
// Логотипы читаются из папки /assets рядом с исполняемым файлом.
|
|
|
|
|
// Принимается формат .png, логотип, представляющий правильный ответ называется godot.png
|
|
|
|
|
func Init() error {
|
2022-01-28 01:00:40 +03:00
|
|
|
|
files, err := ioutil.ReadDir("./assets")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
for _, file := range files {
|
|
|
|
|
|
|
|
|
|
name := file.Name()
|
2022-01-28 16:52:26 +03:00
|
|
|
|
if !strings.HasSuffix(name, ".png") { // Грузим только .png
|
2022-01-28 01:00:40 +03:00
|
|
|
|
continue
|
|
|
|
|
}
|
2022-01-28 16:52:26 +03:00
|
|
|
|
log.Printf("%s", name) // Для отладки выводим имена файлов с логотипами
|
|
|
|
|
path := fmt.Sprintf("./assets/%s", name) // Составляем путь до файла
|
|
|
|
|
im, err := gg.LoadPNG(path) // Грузим png, возвращаем ошибку если что-то идёт не так
|
2022-01-28 01:00:40 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
log.Print(err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
2022-01-28 16:52:26 +03:00
|
|
|
|
is_correct := strings.HasPrefix(name, "godot") // Если грузимый файл -- godot*.png - помечаем его как правильный
|
|
|
|
|
logo := Logo{Image: im, IsCorrect: is_correct} // Создаём в памяти структуру для капчи
|
|
|
|
|
Logos = append(Logos, logo) // Заносим логотип в общий список
|
2022-01-28 01:00:40 +03:00
|
|
|
|
}
|
2022-02-02 00:37:58 +03:00
|
|
|
|
|
|
|
|
|
for i := range Logos {
|
2022-02-02 12:44:47 +03:00
|
|
|
|
XPositions = append(XPositions, 50+i*(ImageWidth-50)/len(Logos)) // Горизонтальное расположение не рандомно: чтобы логотипы не перемешались.
|
2022-02-02 00:37:58 +03:00
|
|
|
|
}
|
2022-01-28 01:00:40 +03:00
|
|
|
|
return nil
|
|
|
|
|
}
|