compositeImages.go 5.1 KB

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