174 lines
4.5 KiB
Go
174 lines
4.5 KiB
Go
|
package service
|
|||
|
|
|||
|
import (
|
|||
|
"bytes"
|
|||
|
"fmt"
|
|||
|
"image"
|
|||
|
"image/jpeg"
|
|||
|
"image/png"
|
|||
|
"os"
|
|||
|
"path/filepath"
|
|||
|
|
|||
|
"github.com/fogleman/gg"
|
|||
|
"github.com/skip2/go-qrcode"
|
|||
|
"github.com/zeromicro/go-zero/core/logx"
|
|||
|
)
|
|||
|
|
|||
|
type ImageService struct {
|
|||
|
baseImagePath string
|
|||
|
}
|
|||
|
|
|||
|
func NewImageService() *ImageService {
|
|||
|
return &ImageService{
|
|||
|
baseImagePath: "static/images", // 原图存放目录
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ProcessImageWithQRCode 处理图片,在中间添加二维码
|
|||
|
func (s *ImageService) ProcessImageWithQRCode(qrcodeType, qrcodeUrl string) ([]byte, string, error) {
|
|||
|
// 1. 根据qrcodeType确定使用哪张背景图
|
|||
|
var backgroundImageName string
|
|||
|
switch qrcodeType {
|
|||
|
case "promote":
|
|||
|
backgroundImageName = "tg_qrcode_1.jpg"
|
|||
|
case "invitation":
|
|||
|
backgroundImageName = "yq_qrcode_1.png"
|
|||
|
default:
|
|||
|
backgroundImageName = "tg_qrcode_1.jpg" // 默认使用第一张图片
|
|||
|
}
|
|||
|
|
|||
|
// 2. 读取原图
|
|||
|
originalImagePath := filepath.Join(s.baseImagePath, backgroundImageName)
|
|||
|
originalImage, err := s.loadImage(originalImagePath)
|
|||
|
if err != nil {
|
|||
|
logx.Errorf("加载原图失败: %v, 图片路径: %s", err, originalImagePath)
|
|||
|
return nil, "", fmt.Errorf("加载原图失败: %v", err)
|
|||
|
}
|
|||
|
|
|||
|
// 3. 获取原图尺寸
|
|||
|
bounds := originalImage.Bounds()
|
|||
|
imgWidth := bounds.Dx()
|
|||
|
imgHeight := bounds.Dy()
|
|||
|
|
|||
|
// 4. 创建绘图上下文
|
|||
|
dc := gg.NewContext(imgWidth, imgHeight)
|
|||
|
|
|||
|
// 5. 绘制原图作为背景
|
|||
|
dc.DrawImageAnchored(originalImage, imgWidth/2, imgHeight/2, 0.5, 0.5)
|
|||
|
|
|||
|
// 6. 生成二维码(去掉白边)
|
|||
|
qrCode, err := qrcode.New(qrcodeUrl, qrcode.Medium)
|
|||
|
if err != nil {
|
|||
|
logx.Errorf("生成二维码失败: %v, 二维码内容: %s", err, qrcodeUrl)
|
|||
|
return nil, "", fmt.Errorf("生成二维码失败: %v", err)
|
|||
|
}
|
|||
|
// 禁用二维码边框,去掉白边
|
|||
|
qrCode.DisableBorder = true
|
|||
|
|
|||
|
// 7. 根据二维码类型设置不同的尺寸和位置
|
|||
|
var qrSize int
|
|||
|
var qrX, qrY int
|
|||
|
|
|||
|
switch qrcodeType {
|
|||
|
case "promote":
|
|||
|
// promote类型:精确设置二维码尺寸
|
|||
|
qrSize = 280 // 固定尺寸280px
|
|||
|
// 左下角位置:距左边和底边留一些边距
|
|||
|
qrX = 192 // 距左边180px
|
|||
|
qrY = imgHeight - qrSize - 190 // 距底边100px
|
|||
|
|
|||
|
case "invitation":
|
|||
|
// invitation类型:精确设置二维码尺寸
|
|||
|
qrSize = 360 // 固定尺寸320px
|
|||
|
// 中间偏上位置
|
|||
|
qrX = (imgWidth - qrSize) / 2 // 水平居中
|
|||
|
qrY = 555 // 垂直位置200px
|
|||
|
|
|||
|
default:
|
|||
|
// 默认(promote样式)
|
|||
|
qrSize = 280 // 固定尺寸280px
|
|||
|
qrX = 200 // 距左边180px
|
|||
|
qrY = imgHeight - qrSize - 200 // 距底边100px
|
|||
|
}
|
|||
|
|
|||
|
// 8. 生成指定尺寸的二维码图片
|
|||
|
qrCodeImage := qrCode.Image(qrSize)
|
|||
|
|
|||
|
// 9. 直接绘制二维码(不添加背景)
|
|||
|
dc.DrawImageAnchored(qrCodeImage, qrX+qrSize/2, qrY+qrSize/2, 0.5, 0.5)
|
|||
|
|
|||
|
// 11. 输出为字节数组
|
|||
|
var buf bytes.Buffer
|
|||
|
err = png.Encode(&buf, dc.Image())
|
|||
|
if err != nil {
|
|||
|
logx.Errorf("编码图片失败: %v", err)
|
|||
|
return nil, "", fmt.Errorf("编码图片失败: %v", err)
|
|||
|
}
|
|||
|
|
|||
|
logx.Infof("成功生成带二维码的图片,类型: %s, 二维码内容: %s, 图片尺寸: %dx%d, 二维码尺寸: %dx%d, 位置: (%d,%d)",
|
|||
|
qrcodeType, qrcodeUrl, imgWidth, imgHeight, qrSize, qrSize, qrX, qrY)
|
|||
|
|
|||
|
return buf.Bytes(), "image/png", nil
|
|||
|
}
|
|||
|
|
|||
|
// loadImage 加载图片文件
|
|||
|
func (s *ImageService) loadImage(path string) (image.Image, error) {
|
|||
|
file, err := os.Open(path)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
defer file.Close()
|
|||
|
|
|||
|
// 尝试解码PNG
|
|||
|
img, err := png.Decode(file)
|
|||
|
if err != nil {
|
|||
|
// 如果PNG解码失败,重新打开文件尝试JPEG
|
|||
|
file.Close()
|
|||
|
file, err = os.Open(path)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
defer file.Close()
|
|||
|
|
|||
|
img, err = jpeg.Decode(file)
|
|||
|
if err != nil {
|
|||
|
// 如果还是失败,使用通用解码器
|
|||
|
file.Close()
|
|||
|
file, err = os.Open(path)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
defer file.Close()
|
|||
|
|
|||
|
img, _, err = image.Decode(file)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return img, nil
|
|||
|
}
|
|||
|
|
|||
|
// GetSupportedImageTypes 获取支持的图片类型列表
|
|||
|
func (s *ImageService) GetSupportedImageTypes() []string {
|
|||
|
return []string{"promote", "invitation"}
|
|||
|
}
|
|||
|
|
|||
|
// CheckImageExists 检查指定类型的背景图是否存在
|
|||
|
func (s *ImageService) CheckImageExists(qrcodeType string) bool {
|
|||
|
var backgroundImageName string
|
|||
|
switch qrcodeType {
|
|||
|
case "promote":
|
|||
|
backgroundImageName = "tg_qrcode_1.jpg"
|
|||
|
case "invitation":
|
|||
|
backgroundImageName = "yq_qrcode_1.png"
|
|||
|
default:
|
|||
|
backgroundImageName = "tg_qrcode_1.jpg"
|
|||
|
}
|
|||
|
|
|||
|
imagePath := filepath.Join(s.baseImagePath, backgroundImageName)
|
|||
|
_, err := os.Stat(imagePath)
|
|||
|
return err == nil
|
|||
|
}
|