2025-02-21 08:57:10 +00:00
|
|
|
|
package utils
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"fmt"
|
|
|
|
|
"image"
|
|
|
|
|
"image/color"
|
|
|
|
|
"image/draw"
|
|
|
|
|
"image/png"
|
|
|
|
|
"math/rand"
|
|
|
|
|
"net/http"
|
|
|
|
|
"os"
|
|
|
|
|
"strconv"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/nfnt/resize"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2025-04-14 06:01:13 +00:00
|
|
|
|
ImgURL = "https://cdns.fontree.cn/fonchain-main/test/image/artwork/config/slidCode_%d.jpg"
|
|
|
|
|
ImgPath = "./code/%d.jpg" //本地路径,暂不使用
|
2025-02-21 08:57:10 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 生成指定范围内的随机数
|
|
|
|
|
func GetNonceByRange(start, end int) int {
|
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
|
return rand.Intn(end-start+1) + start
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取缓冲图片,可能是通过URL或本地路径
|
2025-04-14 06:01:13 +00:00
|
|
|
|
func GetBufferedImage(place int) (image.Image, error) {
|
2025-02-21 08:57:10 +00:00
|
|
|
|
nonce := GetNonceByRange(1, 10)
|
|
|
|
|
var imgURL string
|
|
|
|
|
|
2025-04-14 06:01:13 +00:00
|
|
|
|
place = 0 // 注意,这边的图片现在只使用url图片
|
|
|
|
|
|
|
|
|
|
if place == 0 {
|
2025-02-21 08:57:10 +00:00
|
|
|
|
imgURL = fmt.Sprintf(ImgURL, nonce)
|
|
|
|
|
resp, err := http.Get(imgURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
2025-04-14 06:01:13 +00:00
|
|
|
|
img, _, err := image.Decode(resp.Body)
|
2025-02-21 08:57:10 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-04-14 06:01:13 +00:00
|
|
|
|
return img, nil
|
|
|
|
|
} else {
|
|
|
|
|
file, err := os.Open(fmt.Sprintf(ImgPath, nonce))
|
2025-02-21 08:57:10 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
defer file.Close()
|
2025-04-14 06:01:13 +00:00
|
|
|
|
img, _, err := image.Decode(file)
|
2025-02-21 08:57:10 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-04-14 06:01:13 +00:00
|
|
|
|
return img, nil
|
2025-02-21 08:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 调整图片大小
|
|
|
|
|
func ImageResize(bufferedImage image.Image, width, height int) image.Image {
|
2025-04-14 06:01:13 +00:00
|
|
|
|
return resize.Resize(uint(width), uint(height), bufferedImage, resize.Lanczos3)
|
2025-02-21 08:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CutByTemplate 根据模板裁剪图像 // 生成圆角正方形的滑块图像
|
|
|
|
|
func CutByTemplate(canvasImage, blockImage draw.Image, blockWidth, blockHeight, blockX, blockY int) {
|
|
|
|
|
// 创建水印图像
|
|
|
|
|
waterImage := image.NewRGBA(image.Rect(0, 0, blockWidth, blockHeight))
|
|
|
|
|
blockData := getRoundedSquareData(blockWidth, blockHeight)
|
|
|
|
|
|
|
|
|
|
// 遍历滑块区域,将滑块图案从背景中裁剪出来,并生成水印
|
|
|
|
|
for i := 0; i < blockWidth; i++ {
|
|
|
|
|
for j := 0; j < blockHeight; j++ {
|
|
|
|
|
switch blockData[i][j] {
|
|
|
|
|
case 1: // 实心区域
|
|
|
|
|
blockImage.Set(i, j, canvasImage.At(blockX+i, blockY+j))
|
|
|
|
|
waterImage.Set(i, j, color.Black)
|
|
|
|
|
case 2: // 半透明边缘(不再需要)
|
|
|
|
|
origColor := canvasImage.At(blockX+i, blockY+j)
|
|
|
|
|
r, g, b, _ := origColor.RGBA()
|
|
|
|
|
alpha := uint8(180)
|
|
|
|
|
blockImage.Set(i, j, color.NRGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), alpha})
|
|
|
|
|
waterImage.Set(i, j, color.NRGBA{0, 0, 0, alpha})
|
|
|
|
|
case 3: // 纯白边框区域
|
|
|
|
|
blockImage.Set(i, j, color.White) // 设置纯白色
|
|
|
|
|
waterImage.Set(i, j, color.White)
|
|
|
|
|
default: // 透明区域
|
|
|
|
|
blockImage.Set(i, j, color.Transparent)
|
|
|
|
|
waterImage.Set(i, j, color.Transparent)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 在原始画布上清除滑块区域并设置透明背景
|
|
|
|
|
clearCanvasArea(canvasImage, blockData, blockX, blockY, blockWidth, blockHeight)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取带纯白边框的平滑圆角正方形的块数据
|
|
|
|
|
func getRoundedSquareData(blockWidth, blockHeight int) [][]int {
|
|
|
|
|
data := make([][]int, blockWidth)
|
|
|
|
|
for i := range data {
|
|
|
|
|
data[i] = make([]int, blockHeight)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置圆角半径为正方形边长的 20%
|
|
|
|
|
cornerRadius := float64(blockWidth) * 0.2
|
|
|
|
|
cornerRadiusSquared := cornerRadius * cornerRadius
|
|
|
|
|
|
|
|
|
|
// 设置边框厚度为 1 像素
|
|
|
|
|
borderThickness := 1.0 // 纯白边框宽度为 1 像素
|
|
|
|
|
borderRadiusSquared := (cornerRadius - borderThickness) * (cornerRadius - borderThickness)
|
|
|
|
|
|
|
|
|
|
for i := 0; i < blockWidth; i++ {
|
|
|
|
|
for j := 0; j < blockHeight; j++ {
|
|
|
|
|
// 处理四个角的圆角
|
|
|
|
|
if (i < int(cornerRadius) && j < int(cornerRadius)) || // 左上角
|
|
|
|
|
(i >= blockWidth-int(cornerRadius) && j < int(cornerRadius)) || // 右上角
|
|
|
|
|
(i < int(cornerRadius) && j >= blockHeight-int(cornerRadius)) || // 左下角
|
|
|
|
|
(i >= blockWidth-int(cornerRadius) && j >= blockHeight-int(cornerRadius)) { // 右下角
|
|
|
|
|
|
|
|
|
|
// 计算距离的平方,判断是否在圆角内
|
|
|
|
|
var dx, dy float64
|
|
|
|
|
if i < int(cornerRadius) {
|
|
|
|
|
dx = float64(i) - cornerRadius
|
|
|
|
|
} else {
|
|
|
|
|
dx = float64(i) - float64(blockWidth) + cornerRadius
|
|
|
|
|
}
|
|
|
|
|
if j < int(cornerRadius) {
|
|
|
|
|
dy = float64(j) - cornerRadius
|
|
|
|
|
} else {
|
|
|
|
|
dy = float64(j) - float64(blockHeight) + cornerRadius
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
distanceSquared := dx*dx + dy*dy
|
|
|
|
|
if distanceSquared > cornerRadiusSquared {
|
|
|
|
|
data[i][j] = 0 // 圆角外部区域
|
|
|
|
|
} else if distanceSquared > borderRadiusSquared {
|
|
|
|
|
data[i][j] = 3 // 纯白边框区域
|
|
|
|
|
} else {
|
|
|
|
|
data[i][j] = 1 // 实心区域
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 非圆角区域
|
|
|
|
|
if (i < int(borderThickness) || i >= blockWidth-int(borderThickness)) || // 左右边缘
|
|
|
|
|
(j < int(borderThickness) || j >= blockHeight-int(borderThickness)) { // 上下边缘
|
|
|
|
|
data[i][j] = 3 // 纯白边框区域
|
|
|
|
|
} else {
|
|
|
|
|
data[i][j] = 1 // 实心区域
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 随机生成一个不重叠的滑块区域
|
|
|
|
|
func generateNonOverlappingArea(canvasWidth, canvasHeight, blockX, blockY, blockWidth, blockHeight int) (int, int) {
|
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
|
var newX, newY int
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
// 随机生成新区域的位置
|
|
|
|
|
newX = rand.Intn(canvasWidth - blockWidth)
|
|
|
|
|
newY = rand.Intn(canvasHeight - blockHeight)
|
|
|
|
|
|
|
|
|
|
// 检查新区域是否与原始区域重叠
|
|
|
|
|
if (newX+blockWidth <= blockX || newX >= blockX+blockWidth) &&
|
|
|
|
|
(newY+blockHeight <= blockY || newY >= blockY+blockHeight) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return newX, newY
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将滑块区域设为半透明,并生成不重叠的额外区域
|
|
|
|
|
func clearCanvasArea(canvasImage draw.Image, blockData [][]int, blockX, blockY, blockWidth, blockHeight int) {
|
|
|
|
|
alpha := uint8(128) // 50% 透明度
|
|
|
|
|
|
|
|
|
|
// 设置原始滑块区域为半透明
|
|
|
|
|
for i := 0; i < blockWidth; i++ {
|
|
|
|
|
for j := 0; j < blockHeight; j++ {
|
|
|
|
|
if blockData[i][j] > 0 {
|
|
|
|
|
// 获取原始像素的 RGB 值,并统一设置 Alpha 通道
|
|
|
|
|
originalColor := canvasImage.At(blockX+i, blockY+j)
|
|
|
|
|
r, g, b, _ := originalColor.RGBA()
|
|
|
|
|
|
|
|
|
|
// 设置新的颜色并固定透明度
|
|
|
|
|
semiTransparentColor := color.NRGBA{
|
|
|
|
|
R: uint8(r >> 8),
|
|
|
|
|
G: uint8(g >> 8),
|
|
|
|
|
B: uint8(b >> 8),
|
|
|
|
|
A: alpha, // 固定透明度
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 应用半透明效果
|
|
|
|
|
canvasImage.Set(blockX+i, blockY+j, semiTransparentColor)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成不重叠的新区域位置
|
|
|
|
|
canvasBounds := canvasImage.Bounds()
|
|
|
|
|
newBlockX, newBlockY := generateNonOverlappingArea(canvasBounds.Max.X, canvasBounds.Max.Y, blockX, blockY, blockWidth, blockHeight)
|
|
|
|
|
|
|
|
|
|
// 可以在新区域执行与原始区域相同的操作,或根据需求自定义
|
|
|
|
|
for i := 0; i < blockWidth; i++ {
|
|
|
|
|
for j := 0; j < blockHeight; j++ {
|
|
|
|
|
if blockData[i][j] > 0 {
|
|
|
|
|
// 获取原始像素的 RGB 值,并统一设置 Alpha 通道
|
|
|
|
|
originalColor := canvasImage.At(newBlockX+i, newBlockY+j)
|
|
|
|
|
r, g, b, _ := originalColor.RGBA()
|
|
|
|
|
|
|
|
|
|
// 设置新的颜色并固定透明度
|
|
|
|
|
semiTransparentColor := color.NRGBA{
|
|
|
|
|
R: uint8(r >> 8),
|
|
|
|
|
G: uint8(g >> 8),
|
|
|
|
|
B: uint8(b >> 8),
|
|
|
|
|
A: alpha, // 固定透明度
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 应用半透明效果
|
|
|
|
|
canvasImage.Set(newBlockX+i, newBlockY+j, semiTransparentColor)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// 清除画布上的滑块区域,使其为黑色背景
|
|
|
|
|
//func clearCanvasArea(canvasImage draw.Image, blockData [][]int, blockX, blockY, blockWidth, blockHeight int) {
|
|
|
|
|
// alpha := uint8(128) // 50% 透明度
|
|
|
|
|
//
|
|
|
|
|
// for i := 0; i < blockWidth; i++ {
|
|
|
|
|
// for j := 0; j < blockHeight; j++ {
|
|
|
|
|
// if blockData[i][j] > 0 {
|
|
|
|
|
// // 获取原始像素的 RGB 值,并统一设置 Alpha 通道
|
|
|
|
|
// originalColor := canvasImage.At(blockX+i, blockY+j)
|
|
|
|
|
// r, g, b, _ := originalColor.RGBA()
|
|
|
|
|
//
|
|
|
|
|
// // 设置新的颜色并固定透明度
|
|
|
|
|
// semiTransparentColor := color.NRGBA{
|
|
|
|
|
// R: uint8(r >> 8),
|
|
|
|
|
// G: uint8(g >> 8),
|
|
|
|
|
// B: uint8(b >> 8),
|
|
|
|
|
// A: alpha, // 固定透明度
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// // 应用半透明效果
|
|
|
|
|
// canvasImage.Set(blockX+i, blockY+j, semiTransparentColor)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
//// 根据模板裁剪图像 // 生成拼图类型的滑块图像 有需要打开
|
|
|
|
|
//func CutByTemplate(canvasImage, blockImage draw.Image, blockWidth, blockHeight, blockRadius, blockX, blockY int) (faceY int) {
|
|
|
|
|
// waterImage := image.NewRGBA(image.Rect(0, 0, blockWidth, blockHeight))
|
|
|
|
|
// var blockData [][]int
|
|
|
|
|
// blockData, faceY = getBlockData(blockWidth, blockHeight, blockRadius, blockY)
|
|
|
|
|
//
|
|
|
|
|
// for i := 0; i < blockWidth; i++ {
|
|
|
|
|
// for j := 0; j < blockHeight; j++ {
|
|
|
|
|
// if blockData[i][j] == 1 {
|
|
|
|
|
// // 设置水印背景为黑色
|
|
|
|
|
// waterImage.Set(i, j, color.Black)
|
|
|
|
|
// // 从原始图像中获取像素
|
|
|
|
|
// blockImage.Set(i, j, canvasImage.At(blockX+i, blockY+j))
|
|
|
|
|
//
|
|
|
|
|
// // 设置轮廓为白色
|
|
|
|
|
// if (i+1 < blockWidth && blockData[i+1][j] == 0) ||
|
|
|
|
|
// (j+1 < blockHeight && blockData[i][j+1] == 0) ||
|
|
|
|
|
// (i-1 >= 0 && blockData[i-1][j] == 0) ||
|
|
|
|
|
// (j-1 >= 0 && blockData[i][j-1] == 0) {
|
|
|
|
|
// blockImage.Set(i, j, color.White)
|
|
|
|
|
// waterImage.Set(i, j, color.White)
|
|
|
|
|
// }
|
|
|
|
|
// } else {
|
|
|
|
|
// // 背景设为透明
|
|
|
|
|
// blockImage.Set(i, j, color.Transparent)
|
|
|
|
|
// waterImage.Set(i, j, color.Transparent)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// addBlockWatermark(canvasImage, waterImage, blockX, blockY)
|
|
|
|
|
// return
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//// 获取块数据,用以确定块的形状
|
|
|
|
|
//func getBlockData(blockWidth, blockHeight, blockRadius, blockY int) (blockData [][]int, faceY int) {
|
|
|
|
|
// data := make([][]int, blockWidth)
|
|
|
|
|
// for i := range data {
|
|
|
|
|
// data[i] = make([]int, blockHeight)
|
|
|
|
|
// }
|
|
|
|
|
// rand.Seed(time.Now().UnixNano())
|
|
|
|
|
//
|
|
|
|
|
// // 确定两个随机方向和凸/凹形状
|
|
|
|
|
// face1, face2 := rand.Intn(4), rand.Intn(4)
|
|
|
|
|
// for face1 == face2 {
|
|
|
|
|
// face2 = rand.Intn(4)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// shape := rand.Intn(2) // 0: 凸,1: 凹
|
|
|
|
|
// circle1 := getCircleCoords(face1, blockWidth, blockHeight, blockRadius)
|
|
|
|
|
// circle2 := getCircleCoords(face2, blockWidth, blockHeight, blockRadius)
|
|
|
|
|
//
|
|
|
|
|
// if (face1 == 2 || face2 == 2) && shape == 1 {
|
|
|
|
|
// faceY = blockY - blockRadius
|
|
|
|
|
// } else {
|
|
|
|
|
// faceY = blockY
|
|
|
|
|
// }
|
|
|
|
|
// // 计算轮廓
|
|
|
|
|
// radiusSquared := float64(blockRadius * blockRadius)
|
|
|
|
|
// for i := 0; i < blockWidth; i++ {
|
|
|
|
|
// for j := 0; j < blockHeight; j++ {
|
|
|
|
|
// if i >= blockRadius && i < blockWidth-blockRadius && j >= blockRadius && j < blockHeight-blockRadius {
|
|
|
|
|
// data[i][j] = 1
|
|
|
|
|
// }
|
|
|
|
|
// if circle1 != nil && math.Pow(float64(i-circle1[0]), 2)+math.Pow(float64(j-circle1[1]), 2) <= radiusSquared {
|
|
|
|
|
// data[i][j] = shape
|
|
|
|
|
// }
|
|
|
|
|
// if circle2 != nil && math.Pow(float64(i-circle2[0]), 2)+math.Pow(float64(j-circle2[1]), 2) <= radiusSquared {
|
|
|
|
|
// data[i][j] = shape
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// return data, faceY
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//// 根据方向获取圆心坐标
|
|
|
|
|
//func getCircleCoords(face, blockWidth, blockHeight, blockRadius int) []int {
|
|
|
|
|
// switch face {
|
|
|
|
|
// case 0:
|
|
|
|
|
// return []int{blockWidth / 2, blockRadius} // 上
|
|
|
|
|
// case 1:
|
|
|
|
|
// return []int{blockRadius, blockHeight / 2} // 左
|
|
|
|
|
// case 2:
|
|
|
|
|
// return []int{blockWidth / 2, blockHeight - blockRadius - 1} // 下
|
|
|
|
|
// case 3:
|
|
|
|
|
// return []int{blockWidth - blockRadius - 1, blockHeight / 2} // 右
|
|
|
|
|
// default:
|
|
|
|
|
// return nil
|
|
|
|
|
// }
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//// 在画布图像上添加水印
|
|
|
|
|
//func addBlockWatermark(canvasImage, waterImage draw.Image, blockX, blockY int) {
|
|
|
|
|
// for i := 0; i < waterImage.Bounds().Dx(); i++ {
|
|
|
|
|
// for j := 0; j < waterImage.Bounds().Dy(); j++ {
|
|
|
|
|
// if _, _, _, alpha := waterImage.At(i, j).RGBA(); alpha > 0 {
|
|
|
|
|
// canvasImage.Set(blockX+i, blockY+j, waterImage.At(i, j))
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
// 将图像转换为Base64编码
|
|
|
|
|
func ToBase64(img image.Image, format string) string {
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
|
switch format {
|
|
|
|
|
case "png":
|
|
|
|
|
png.Encode(buf, img)
|
|
|
|
|
}
|
|
|
|
|
return base64.StdEncoding.EncodeToString(buf.Bytes())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算绝对值
|
|
|
|
|
func Abs(a int) int {
|
|
|
|
|
if a < 0 {
|
|
|
|
|
return -a
|
|
|
|
|
}
|
|
|
|
|
return a
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将字符串转换为整数
|
|
|
|
|
func ParseInt(s string) int {
|
|
|
|
|
i, _ := strconv.Atoi(s)
|
|
|
|
|
return i
|
|
|
|
|
}
|