Skip to content

Commit

Permalink
增加文字转图片
Browse files Browse the repository at this point in the history
  • Loading branch information
bitqiu committed Jun 11, 2024
1 parent 5054696 commit 75d77d6
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 73 deletions.
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/cast v1.6.0
golang.org/x/image v0.16.0
)

require (
Expand All @@ -22,7 +24,6 @@ require (
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
Expand All @@ -32,11 +33,9 @@ require (
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/image v0.16.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
19 changes: 10 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
Expand All @@ -30,17 +31,17 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
Expand All @@ -56,10 +57,12 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down Expand Up @@ -91,8 +94,6 @@ golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
49 changes: 46 additions & 3 deletions handler/captcha.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
package handler

import (
"bytes"
"fmt"
"github.com/bitqiu/pix-gen/fonts"
"github.com/bitqiu/pix-gen/pkg/captcha"
"github.com/gin-gonic/gin"
"github.com/spf13/cast"
"image/png"
"net/http"
)

// HandleCaptcha 处理验证码生成请求的处理程序
func HandleCaptcha(c *gin.Context) {

// 获取并解析 width 和 height 参数,如果不存在则使用默认值
widthStr := c.DefaultQuery("width", fmt.Sprintf("%d", 120))
heightStr := c.DefaultQuery("height", fmt.Sprintf("%d", 30))
width := cast.ToInt(c.DefaultQuery("width", "120"))
height := cast.ToInt(c.DefaultQuery("height", "30"))
code := c.Query("code")

// 调用 captcha 包生成验证码
captchaImage, err := captcha.GenerateCaptcha(widthStr, heightStr, code)
captchaImage, err := generateCaptcha(width, height, code)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
Expand All @@ -26,3 +30,42 @@ func HandleCaptcha(c *gin.Context) {
c.Data(http.StatusOK, "image/png", captchaImage)

}

// generateCaptcha 生成验证码图片
func generateCaptcha(width, height int, code string) ([]byte, error) {
// 初始化验证码生成器
cap := captcha.New()
// 设置干扰模式
cap.SetDisturbance(captcha.NORMAL)

// 读取字体文件
fontBytes, err := fonts.FontsFS.ReadFile("MiSans-Normal.ttf")
if err != nil {
return nil, fmt.Errorf("invalid font file")
}

// 添加字体到验证码生成器
err = cap.AddFontFromBytes(fontBytes)
if err != nil {
return nil, fmt.Errorf("invalid font")
}

// 检查 width 和 height 的边界条件
if width <= 0 || height <= 0 {
return nil, fmt.Errorf("width and height must be positive integers")
}

// 设置验证码图片的大小
cap.SetSize(width, height)

// 生成新的验证码
img := cap.CreateCustom(code)

// 将图像编码为 PNG 并输出二进制图像数据
buffer := new(bytes.Buffer)
if err := png.Encode(buffer, img); err != nil {
return nil, fmt.Errorf("failed to encode image")
}

return buffer.Bytes(), nil
}
116 changes: 116 additions & 0 deletions handler/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package handler

import (
"bytes"
"fmt"
"github.com/bitqiu/pix-gen/fonts"
"github.com/gin-gonic/gin"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"github.com/spf13/cast"
"golang.org/x/image/font"
"image"
"image/color"
"image/draw"
"image/png"
"math"
"net/http"
)

// HandleImage 是处理生成二维码请求的处理程序
func HandleImage(c *gin.Context) {
text := c.DefaultQuery("text", "null") // 获取二维码的内容,默认为 "null"
width := cast.ToInt(c.DefaultQuery("width", "500")) // 获取图片宽度,默认为 800
height := cast.ToInt(c.DefaultQuery("height", "100")) // 获取图片高度,默认为 600
tipText := c.DefaultQuery("tipText", "请通过图片和复制的地址核对一样后进行转账") // 获取自定义文字,默认为 "Custom Text"

// 调用 generateImage 函数生成图像
imageData, err := generateImage(text, tipText, width, height)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

// 返回生成的图像
c.Data(http.StatusOK, "image/png", imageData)
}

// generateImage 生成带有指定文字的图像
func generateImage(text, tipText string, width, height int) ([]byte, error) {
// 读取字体数据
fontBytes, err := fonts.FontsFS.ReadFile("MiSans-Normal.ttf")
if err != nil {
return nil, fmt.Errorf("读取字体文件出错: %v", err)
}

// 解析字体
parsedFont, err := freetype.ParseFont(fontBytes)
if err != nil {
return nil, fmt.Errorf("解析字体出错: %v", err)
}

// 根据图像尺寸动态计算字体大小
area := float64(width * height)
fontSize := math.Sqrt(area / float64(100))

// 创建一个新的 RGBA 图像,背景为白色
img := image.NewRGBA(image.Rect(0, 0, width, height))
bgColor := color.RGBA{255, 255, 255, 255}
draw.Draw(img, img.Bounds(), &image.Uniform{bgColor}, image.Point{}, draw.Src)

// 初始化 freetype 上下文
ctx := freetype.NewContext()
ctx.SetDPI(72)
ctx.SetFont(parsedFont)
ctx.SetFontSize(fontSize)
ctx.SetClip(img.Bounds())
ctx.SetDst(img)
ctx.SetSrc(image.Black)

// 计算文字的尺寸
opts := &truetype.Options{
Size: fontSize,
DPI: 72,
}
face := truetype.NewFace(parsedFont, opts)
textWidth, textHeight := getTextDimensions(face, text)
tipTextWidth, tipTextHeight := getTextDimensions(face, tipText)

// 计算文本块的总高度
totalTextHeight := textHeight + tipTextHeight + 10 // 两行文字间距 10 像素

// 计算起始点,使文本水平和垂直居中
x := (width - textWidth) / 2
tipX := (width - tipTextWidth) / 2
y := (height - totalTextHeight) / 2

// 绘制主文字
pt := freetype.Pt(x, y+int(ctx.PointToFixed(fontSize)>>6))
if _, err := ctx.DrawString(text, pt); err != nil {
return nil, fmt.Errorf("绘制文字出错: %v", err)
}

// 用红色绘制自定义提示文字
ctx.SetSrc(image.NewUniform(color.RGBA{255, 0, 0, 255}))
tipY := y + textHeight + 10
tipPt := freetype.Pt(tipX, tipY+int(ctx.PointToFixed(fontSize)>>6))
if _, err := ctx.DrawString(tipText, tipPt); err != nil {
return nil, fmt.Errorf("绘制自定义文字出错: %v", err)
}

// 将图像编码为 PNG 格式
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return nil, fmt.Errorf("编码图像出错: %v", err)
}

return buf.Bytes(), nil
}

// getTextDimensions 计算给定文字的宽度和高度
func getTextDimensions(face font.Face, text string) (int, int) {
bounds, _ := font.BoundString(face, text)
width := (bounds.Max.X - bounds.Min.X).Ceil()
height := (bounds.Max.Y - bounds.Min.Y).Ceil()
return width, height
}
7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ func main() {
config.AllowOrigins = []string{"*"}
r.Use(cors.New(config))

r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "ok",
})
})

r.GET("/captcha", handler.HandleCaptcha)
r.GET("/qrcode", handler.HandleQrcode)
r.GET("/image", handler.HandleImage)
r.Run(":8080")
}
58 changes: 0 additions & 58 deletions pkg/captcha/gen.go

This file was deleted.

0 comments on commit 75d77d6

Please sign in to comment.