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 }