|
@@ -0,0 +1,200 @@
|
|
|
+package common
|
|
|
+
|
|
|
+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 := 160 // 二维码原始大小(像素)
|
|
|
+ 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
|
|
|
+}
|