package service import ( "bytes" "fmt" "github.com/disintegration/imaging" "github.com/fogleman/gg" "github.com/gogf/gf/v2/util/gconv" "github.com/nfnt/resize" "github.com/skip2/go-qrcode" "image" "image/color" "image/draw" "image/jpeg" "image/png" "math" "net/http" ) func CompositeImage(backgroundURL, qrContent string) (err error, imgByte []byte) { // 配置参数 //backgroundURL := "https://images.unsplash.com/photo-1501854140801-50d01698950b" // 示例背景图片 //qrContent := "https://github.com/zeromicro/go-zero" // 二维码内容 //outputPath := "output.jpg" // 输出文件路径 //margin := 20 // 二维码距边缘的边距 rightMargin := 60 bottomMargin := 100 qrSize := 140 // 二维码原始大小(像素) cornerRadius := 5.0 // 二维码圆角半径 borderCornerRadius := 10.0 borderSize := 10 // 二维码白色边框大小 shadowBlur := 0.0 // 阴影模糊度 shadowOpacity := 0.0 // 阴影透明度 // 创建带效果的二维码并合成到背景右下角 imgByte, err = createQRComposite(backgroundURL, qrContent, rightMargin, bottomMargin, qrSize, cornerRadius, borderCornerRadius, gconv.Float64(borderSize), shadowBlur, shadowOpacity) if err != nil { return err, nil } return nil, imgByte } // 创建二维码合成图片 func createQRComposite(backgroundURL, qrContent string, rightMargin, bottomMargin, qrSize int, cornerRadius, borderCornerRadius, borderSize, shadowBlur, shadowOpacity float64) ([]byte, error) { // 1. 下载背景图片 bgImg, imgType, err := downloadImage(backgroundURL) if err != nil { return nil, err } // 2. 生成带效果的二维码 qrImg, err := generateStyledQRCode(qrContent, qrSize, color.Black, color.White, cornerRadius, borderCornerRadius, borderSize, shadowBlur, shadowOpacity) if err != nil { return nil, err } // 3. 计算二维码在右下角的位置 bgBounds := bgImg.Bounds() qrBounds := qrImg.Bounds() pos := image.Point{ X: bgBounds.Dx() - qrBounds.Dx() - rightMargin, Y: bgBounds.Dy() - qrBounds.Dy() - bottomMargin, } // 4. 调整二维码大小(如果需要) finalQR := qrImg if bgBounds.Dx() < 800 { // 背景图较小,缩小二维码 targetWidth := bgBounds.Dx() / 4 finalQR = resize.Resize(uint(targetWidth), 0, qrImg, resize.Lanczos3) // 重新计算位置 pos = image.Point{ X: bgBounds.Dx() - finalQR.Bounds().Dx() - rightMargin, Y: bgBounds.Dy() - finalQR.Bounds().Dy() - bottomMargin, } } // 5. 合成图片 resultImg := image.NewRGBA(bgBounds) draw.Draw(resultImg, bgBounds, bgImg, image.Point{}, draw.Src) draw.Draw(resultImg, finalQR.Bounds().Add(pos), finalQR, image.Point{}, draw.Over) // 6. 保存结果 //return saveImage(outputPath, resultImg, 95) imgByte, err := encodeImageToBytes(resultImg, imgType, 90) if err != nil { return nil, err } return imgByte, nil } // 下载图片 func downloadImage(url string) (image.Image, string, error) { resp, err := http.Get(url) if err != nil { return nil, "", err } defer resp.Body.Close() img, imgType, err := image.Decode(resp.Body) if err != nil { return nil, "", err } return img, imgType, nil } func generateStyledQRCode(content string, size int, fgColor, bgColor color.Color, cornerRadius, borderCornerRadius, borderSize, shadowBlur, shadowOpacity float64) (image.Image, error) { // 生成基本二维码 qr, err := qrcode.New(content, qrcode.Highest) if err != nil { return nil, err } qr.ForegroundColor = fgColor qr.BackgroundColor = bgColor qr.DisableBorder = true // 创建基本二维码图片 baseImg := qr.Image(size) // 计算带边框的总大小 borderWidth := int(math.Ceil(borderSize)) totalSize := size + 2*borderWidth // 创建带透明背景的总画布 dc := gg.NewContext(totalSize, totalSize) dc.SetColor(color.Transparent) dc.Clear() // 1. 绘制圆角白色边框 dc.SetColor(color.White) dc.DrawRoundedRectangle(0, 0, float64(totalSize), float64(totalSize), borderCornerRadius+borderSize) dc.Fill() // 2. 创建二维码的圆角蒙版 qrDC := gg.NewContext(size, size) qrDC.SetColor(color.Transparent) qrDC.Clear() if cornerRadius > 0 { qrDC.DrawRoundedRectangle(0, 0, float64(size), float64(size), cornerRadius) qrDC.Clip() } // 3. 绘制二维码到蒙版 qrDC.DrawImage(baseImg, 0, 0) // 4. 将带蒙版的二维码绘制到总画布(居中) dc.DrawImage(qrDC.Image(), borderWidth, borderWidth) // 添加阴影效果 if shadowBlur > 0 && shadowOpacity > 0 { shadowImg := addShadow(dc.Image(), shadowBlur, shadowOpacity) return shadowImg, nil } return dc.Image(), nil } // 添加阴影效果 func addShadow(img image.Image, blur, opacity float64) image.Image { bounds := img.Bounds() dc := gg.NewContext(bounds.Dx()+int(blur*2), bounds.Dy()+int(blur*2)) // 绘制阴影 dc.SetRGBA(0, 0, 0, opacity) dc.DrawRectangle(blur, blur, float64(bounds.Dx()), float64(bounds.Dy())) dc.Fill() // 应用高斯模糊 shadow := dc.Image() blurred := imaging.Blur(shadow, blur) // 创建新画布绘制二维码和阴影 dc = gg.NewContext(bounds.Dx()+int(blur*2), bounds.Dy()+int(blur*2)) dc.DrawImage(blurred, 0, 0) dc.DrawImage(img, int(blur), int(blur)) return dc.Image() } // 编码图片为字节流 func encodeImageToBytes(img image.Image, format string, quality int) ([]byte, error) { buf := new(bytes.Buffer) switch format { case "png": if err := png.Encode(buf, img); err != nil { return nil, err } case "jpg", "jpeg": if err := jpeg.Encode(buf, img, &jpeg.Options{Quality: quality}); err != nil { return nil, err } default: return nil, fmt.Errorf("不支持的图片格式: %s", format) } return buf.Bytes(), nil }