compositeImages.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package service
  2. import (
  3. "bytes"
  4. "fmt"
  5. "github.com/disintegration/imaging"
  6. "github.com/fogleman/gg"
  7. "github.com/gogf/gf/v2/util/gconv"
  8. "github.com/nfnt/resize"
  9. "github.com/skip2/go-qrcode"
  10. "image"
  11. "image/color"
  12. "image/draw"
  13. "image/jpeg"
  14. "image/png"
  15. "math"
  16. "net/http"
  17. )
  18. func CompositeImage(backgroundURL, qrContent string) (err error, imgByte []byte) {
  19. // 配置参数
  20. //backgroundURL := "https://images.unsplash.com/photo-1501854140801-50d01698950b" // 示例背景图片
  21. //qrContent := "https://github.com/zeromicro/go-zero" // 二维码内容
  22. //outputPath := "output.jpg" // 输出文件路径
  23. //margin := 20 // 二维码距边缘的边距
  24. rightMargin := 60
  25. bottomMargin := 100
  26. qrSize := 140 // 二维码原始大小(像素)
  27. cornerRadius := 5.0 // 二维码圆角半径
  28. borderCornerRadius := 10.0
  29. borderSize := 10 // 二维码白色边框大小
  30. shadowBlur := 0.0 // 阴影模糊度
  31. shadowOpacity := 0.0 // 阴影透明度
  32. // 创建带效果的二维码并合成到背景右下角
  33. imgByte, err = createQRComposite(backgroundURL, qrContent, rightMargin, bottomMargin, qrSize, cornerRadius, borderCornerRadius, gconv.Float64(borderSize), shadowBlur, shadowOpacity)
  34. if err != nil {
  35. return err, nil
  36. }
  37. return nil, imgByte
  38. }
  39. // 创建二维码合成图片
  40. func createQRComposite(backgroundURL, qrContent string, rightMargin, bottomMargin, qrSize int, cornerRadius, borderCornerRadius, borderSize, shadowBlur, shadowOpacity float64) ([]byte, error) {
  41. // 1. 下载背景图片
  42. bgImg, imgType, err := downloadImage(backgroundURL)
  43. if err != nil {
  44. return nil, err
  45. }
  46. // 2. 生成带效果的二维码
  47. qrImg, err := generateStyledQRCode(qrContent, qrSize, color.Black, color.White, cornerRadius, borderCornerRadius, borderSize, shadowBlur, shadowOpacity)
  48. if err != nil {
  49. return nil, err
  50. }
  51. // 3. 计算二维码在右下角的位置
  52. bgBounds := bgImg.Bounds()
  53. qrBounds := qrImg.Bounds()
  54. pos := image.Point{
  55. X: bgBounds.Dx() - qrBounds.Dx() - rightMargin,
  56. Y: bgBounds.Dy() - qrBounds.Dy() - bottomMargin,
  57. }
  58. // 4. 调整二维码大小(如果需要)
  59. finalQR := qrImg
  60. if bgBounds.Dx() < 800 {
  61. // 背景图较小,缩小二维码
  62. targetWidth := bgBounds.Dx() / 4
  63. finalQR = resize.Resize(uint(targetWidth), 0, qrImg, resize.Lanczos3)
  64. // 重新计算位置
  65. pos = image.Point{
  66. X: bgBounds.Dx() - finalQR.Bounds().Dx() - rightMargin,
  67. Y: bgBounds.Dy() - finalQR.Bounds().Dy() - bottomMargin,
  68. }
  69. }
  70. // 5. 合成图片
  71. resultImg := image.NewRGBA(bgBounds)
  72. draw.Draw(resultImg, bgBounds, bgImg, image.Point{}, draw.Src)
  73. draw.Draw(resultImg, finalQR.Bounds().Add(pos), finalQR, image.Point{}, draw.Over)
  74. // 6. 保存结果
  75. //return saveImage(outputPath, resultImg, 95)
  76. imgByte, err := encodeImageToBytes(resultImg, imgType, 90)
  77. if err != nil {
  78. return nil, err
  79. }
  80. return imgByte, nil
  81. }
  82. // 下载图片
  83. func downloadImage(url string) (image.Image, string, error) {
  84. resp, err := http.Get(url)
  85. if err != nil {
  86. return nil, "", err
  87. }
  88. defer resp.Body.Close()
  89. img, imgType, err := image.Decode(resp.Body)
  90. if err != nil {
  91. return nil, "", err
  92. }
  93. return img, imgType, nil
  94. }
  95. func generateStyledQRCode(content string, size int, fgColor, bgColor color.Color, cornerRadius, borderCornerRadius, borderSize, shadowBlur, shadowOpacity float64) (image.Image, error) {
  96. // 生成基本二维码
  97. qr, err := qrcode.New(content, qrcode.Highest)
  98. if err != nil {
  99. return nil, err
  100. }
  101. qr.ForegroundColor = fgColor
  102. qr.BackgroundColor = bgColor
  103. qr.DisableBorder = true
  104. // 创建基本二维码图片
  105. baseImg := qr.Image(size)
  106. // 计算带边框的总大小
  107. borderWidth := int(math.Ceil(borderSize))
  108. totalSize := size + 2*borderWidth
  109. // 创建带透明背景的总画布
  110. dc := gg.NewContext(totalSize, totalSize)
  111. dc.SetColor(color.Transparent)
  112. dc.Clear()
  113. // 1. 绘制圆角白色边框
  114. dc.SetColor(color.White)
  115. dc.DrawRoundedRectangle(0, 0, float64(totalSize), float64(totalSize), borderCornerRadius+borderSize)
  116. dc.Fill()
  117. // 2. 创建二维码的圆角蒙版
  118. qrDC := gg.NewContext(size, size)
  119. qrDC.SetColor(color.Transparent)
  120. qrDC.Clear()
  121. if cornerRadius > 0 {
  122. qrDC.DrawRoundedRectangle(0, 0, float64(size), float64(size), cornerRadius)
  123. qrDC.Clip()
  124. }
  125. // 3. 绘制二维码到蒙版
  126. qrDC.DrawImage(baseImg, 0, 0)
  127. // 4. 将带蒙版的二维码绘制到总画布(居中)
  128. dc.DrawImage(qrDC.Image(), borderWidth, borderWidth)
  129. // 添加阴影效果
  130. if shadowBlur > 0 && shadowOpacity > 0 {
  131. shadowImg := addShadow(dc.Image(), shadowBlur, shadowOpacity)
  132. return shadowImg, nil
  133. }
  134. return dc.Image(), nil
  135. }
  136. // 添加阴影效果
  137. func addShadow(img image.Image, blur, opacity float64) image.Image {
  138. bounds := img.Bounds()
  139. dc := gg.NewContext(bounds.Dx()+int(blur*2), bounds.Dy()+int(blur*2))
  140. // 绘制阴影
  141. dc.SetRGBA(0, 0, 0, opacity)
  142. dc.DrawRectangle(blur, blur, float64(bounds.Dx()), float64(bounds.Dy()))
  143. dc.Fill()
  144. // 应用高斯模糊
  145. shadow := dc.Image()
  146. blurred := imaging.Blur(shadow, blur)
  147. // 创建新画布绘制二维码和阴影
  148. dc = gg.NewContext(bounds.Dx()+int(blur*2), bounds.Dy()+int(blur*2))
  149. dc.DrawImage(blurred, 0, 0)
  150. dc.DrawImage(img, int(blur), int(blur))
  151. return dc.Image()
  152. }
  153. // 编码图片为字节流
  154. func encodeImageToBytes(img image.Image, format string, quality int) ([]byte, error) {
  155. buf := new(bytes.Buffer)
  156. switch format {
  157. case "png":
  158. if err := png.Encode(buf, img); err != nil {
  159. return nil, err
  160. }
  161. case "jpg", "jpeg":
  162. if err := jpeg.Encode(buf, img, &jpeg.Options{Quality: quality}); err != nil {
  163. return nil, err
  164. }
  165. default:
  166. return nil, fmt.Errorf("不支持的图片格式: %s", format)
  167. }
  168. return buf.Bytes(), nil
  169. }